149
148
from ivle import (util, chat, caps)
150
149
from ivle.conf import (usrmgt_host, usrmgt_port, usrmgt_magic)
152
from ivle.auth import authenticate
153
from ivle.auth.autherror import AuthError
150
import ivle.pulldown_subj
152
from ivle.rpc.decorators import require_method, require_cap
154
from ivle.auth import AuthError, authenticate
156
157
# The user must send this declaration message to ensure they acknowledge the
158
159
USER_DECLARATION = "I accept the IVLE Terms of Service"
161
# List of fields returned as part of the user JSON dictionary
162
# (as returned by the get_user action)
164
"login", "state", "unixid", "email", "nick", "fullname",
165
"rolenm", "studentid", "acct_exp", "pass_exp", "last_login",
161
170
"""Handler for the Console Service AJAX backend application."""
162
171
if req.user is None:
211
217
# Make sure the user's status is "no_agreement", and set status to
212
218
# pending, within the one transaction. This ensures we only do this
214
db.start_transaction()
217
user_details = db.get_user(req.user.login)
218
221
# Check that the user's status is "no_agreement".
219
222
# (Both to avoid redundant calls, and to stop disabled users from
220
223
# re-enabling their accounts).
221
if user_details.state != "no_agreement":
224
if req.user.state != "no_agreement":
222
225
req.throw_error(req.HTTP_BAD_REQUEST,
223
226
"You have already agreed to the terms.")
224
227
# Write state "pending" to ensure we don't try this again
225
db.update_user(req.user.login, state="pending")
228
req.user.state = u"pending"
231
234
# Get the arguments for usermgt.activate_user from the session
232
235
# (The user must have already logged in to use this app)
249
255
status = 'failure'
251
257
if status == 'okay':
252
req.user.state = "enabled"
258
req.user.state = u"enabled"
254
260
# Reset the user back to no agreement
255
req.user.state = "no_agreement"
256
db.update_user(req.user.login, state="no_agreement")
259
# Update the users state
260
session = req.get_session()
261
session['user'] = req.user
261
req.user.state = u"no_agreement"
264
264
# Write the response
265
265
req.content_type = "text/plain"
266
266
req.write(cjson.encode(response))
270
271
create_user_fields_required = [
271
272
'login', 'fullname', 'rolenm'
335
334
'password', 'nick', 'email', 'rolenm', 'unixid', 'fullname',
338
@require_method('POST')
338
339
def handle_update_user(req, fields):
339
340
"""Update a user's account details.
340
341
This can be done in a limited form by any user, on their own account,
341
342
or with full powers by a user with CAP_UPDATEUSER on any account.
343
if req.method != "POST":
344
req.throw_error(req.HTTP_METHOD_NOT_ALLOWED,
345
"Only POST requests are valid methods to update_user.")
347
344
# Only give full powers if this user has CAP_UPDATEUSER
348
345
fullpowers = req.user.hasCap(caps.CAP_UPDATEUSER)
349
346
# List of fields that may be changed
362
359
# If login not specified, update yourself
363
360
login = req.user.login
362
user = ivle.database.User.get_by_login(req.store, login)
365
364
# Make a dict of fields to update
367
365
oldpassword = fields.getfirst('oldpass')
369
366
for f in fieldlist:
370
367
val = fields.getfirst(f)
371
368
if val is not None:
369
# Note: May be rolled back if auth check below fails
370
setattr(user, f, val.value.decode('utf-8'))
376
if 'password' in update:
374
# If the user is trying to set a new password, check that they have
375
# entered old password and it authenticates.
376
if fields.getfirst('password') is not None:
378
378
authenticate.authenticate(login, oldpassword)
379
379
except AuthError:
380
380
req.headers_out['X-IVLE-Action-Error'] = \
381
381
urllib.quote("Old password incorrect.")
382
382
req.status = req.HTTP_BAD_REQUEST
383
# Cancel all the changes made to user (including setting new pass)
385
update['login'] = login
388
db.update_user(**update)
390
# Re-read the user's details from the DB so we can update their session
391
# XXX potentially-unsafe session write
392
if login == req.user.login:
393
user = db.get_user(login)
394
session = req.get_session()
396
session['user'] = user
402
389
req.content_type = "text/plain"
424
411
login = req.user.login
426
413
# Just talk direct to the DB
428
user = db.get_user(login)
432
user['rolenm'] = str(user['role'])
414
user = ivle.database.User.get_by_login(req.store, login)
415
user = ivle.util.object_to_dict(user_fields_list, user)
438
416
# Convert time stamps to nice strings
440
if user['pass_exp'] is not None:
441
user['pass_exp'] = str(user['pass_exp'])
445
if user['acct_exp'] is not None:
446
user['acct_exp'] = str(user['acct_exp'])
450
if user['last_login'] is not None:
451
user['last_login'] = str(user['last_login'])
417
for k in 'pass_exp', 'acct_exp', 'last_login':
418
if user[k] is not None:
419
user[k] = unicode(user[k])
454
421
response = cjson.encode(user)
455
422
req.content_type = "text/plain"
456
423
req.write(response)
466
433
##fullpowers = req.user.hasCap(caps.CAP_GETUSER)
469
login = fields.getfirst('login')
436
user = ivle.database.User.get_by_login(req.store,
437
fields.getfirst('login'))
471
439
raise AttributeError()
472
if not fullpowers and login != req.user.login:
440
if not fullpowers and user != req.user:
473
441
# Not allowed to edit other users
474
442
req.throw_error(req.HTTP_FORBIDDEN,
475
443
"You do not have permission to see another user's subjects.")
476
444
except AttributeError:
477
445
# If login not specified, update yourself
478
login = req.user.login
480
# Just talk direct to the DB
482
enrolments = db.get_enrolment(login)
484
e['groups'] = db.get_enrolment_groups(login, e['offeringid'])
486
response = cjson.encode(enrolments)
449
for e in user.active_enrolments:
450
dict_enrolments.append({
451
'offeringid': e.offering.id,
452
'subj_code': e.offering.subject.code,
453
'subj_name': e.offering.subject.name,
454
'subj_short_name': e.offering.subject.short_name,
455
'url': e.offering.subject.url,
456
'year': e.offering.semester.year,
457
'semester': e.offering.semester.semester,
458
'groups': [{'name': group.name,
459
'nick': group.nick} for group in e.groups]
461
response = cjson.encode(dict_enrolments)
487
462
req.content_type = "text/plain"
488
463
req.write(response)
504
479
req.throw_error(req.HTTP_BAD_REQUEST,
505
480
"subjectid must be a integer")
509
offerings = db.get_offering_semesters(subjectid)
513
response = cjson.encode([o for o in offerings if o['active']])
482
subject = req.store.get(ivle.database.Subject, subjectid)
484
response = cjson.encode([{'offeringid': offering.id,
485
'subj_name': offering.subject.name,
486
'year': offering.semester.year,
487
'semester': offering.semester.semester,
488
'active': offering.semester.active
489
} for offering in subject.offerings
490
if offering.semester.active])
514
491
req.content_type = "text/plain"
515
492
req.write(response)
531
508
req.throw_error(req.HTTP_BAD_REQUEST,
532
509
"offeringid must be a integer")
536
projectsets = db.get_projectsets_by_offering(offeringid)
537
for p in projectsets:
538
p['groups'] = db.get_groups_by_projectset(p['projectsetid'])
540
req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR, repr(e))
544
response = cjson.encode(projectsets)
547
def handle_create_project_set(req, fields):
548
"""Required cap: CAP_MANAGEPROJECTS
549
Creates a project set for a offering - returns the projectsetid
551
offeringid, max_students_per_group
554
if req.method != "POST":
555
req.throw_error(req.HTTP_METHOD_NOT_ALLOWED,
556
"Only POST requests are valid methods to create_user.")
557
# Check if this user has CAP_MANAGEPROJECTS
558
if not req.user.hasCap(caps.CAP_MANAGEPROJECTS):
559
req.throw_error(req.HTTP_FORBIDDEN,
560
"You do not have permission to manage projects.")
561
# Get required fields
562
offeringid = fields.getfirst('offeringid')
563
max_students_per_group = fields.getfirst('max_students_per_group')
564
if offeringid is None or max_students_per_group is None:
565
req.throw_error(req.HTTP_BAD_REQUEST,
566
"Required: offeringid, max_students_per_group")
570
dbquery = db.return_insert(
572
'offeringid': offeringid,
573
'max_students_per_group': max_students_per_group,
576
frozenset(["offeringid", "max_students_per_group"]),
581
response = cjson.encode(dbquery.dictresult()[0])
583
req.content_type = "text/plain"
586
def handle_create_project(req, fields):
587
"""Required cap: CAP_MANAGEPROJECTS
588
Creates a project in a specific project set
592
synopsis, url, deadline
597
if req.method != "POST":
598
req.throw_error(req.HTTP_METHOD_NOT_ALLOWED,
599
"Only POST requests are valid methods to create_user.")
600
# Check if this user has CAP_MANAGEPROJECTS
601
if not req.user.hasCap(caps.CAP_MANAGEPROJECTS):
602
req.throw_error(req.HTTP_FORBIDDEN,
603
"You do not have permission to manage projects.")
604
# Get required fields
605
projectsetid = fields.getfirst('projectsetid')
606
if projectsetid is None:
607
req.throw_error(req.HTTP_BAD_REQUEST,
608
"Required: projectsetid")
609
# Get optional fields
610
synopsis = fields.getfirst('synopsis')
611
url = fields.getfirst('url')
612
deadline = fields.getfirst('deadline')
613
if deadline is not None:
615
deadline = util.parse_iso8601(deadline).timetuple()
616
except ValueError, e:
617
req.throw_error(req.HTTP_BAD_REQUEST, e.message)
622
dbquery = db.return_insert(
624
'projectsetid': projectsetid,
625
'synopsis': synopsis,
627
'deadline': deadline,
630
frozenset(["projectsetid", "synopsis", "url", "deadline"]),
631
["projectid"], # returns
634
req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR, repr(e))
638
response = cjson.encode(dbquery.dictresult()[0])
640
req.content_type = "text/plain"
511
offering = req.store.get(ivle.database.Offering, offeringid)
513
dict_projectsets = []
515
for p in offering.project_sets:
516
dict_projectsets.append({
517
'projectsetid': p.id,
518
'max_students_per_group': p.max_students_per_group,
519
'groups': [{'groupid': g.id,
521
'nick': g.nick} for g in p.project_groups]
524
req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR, repr(e))
526
response = cjson.encode(dict_projectsets)
529
@require_method('POST')
530
@require_cap(caps.CAP_MANAGEGROUPS)
643
531
def handle_create_group(req, fields):
644
532
"""Required cap: CAP_MANAGEGROUPS
645
533
Creates a project group in a specific project set
653
if req.method != "POST":
654
req.throw_error(req.HTTP_METHOD_NOT_ALLOWED,
655
"Only POST requests are valid methods to create_user.")
656
# Check if this is allowed to manage groups
657
if not req.user.hasCap(caps.CAP_MANAGEGROUPS):
658
req.throw_error(req.HTTP_FORBIDDEN,
659
"You do not have permission to manage groups.")
660
541
# Get required fields
661
projectsetid = fields.getfirst('projectsetid')
662
groupnm = fields.getfirst('groupnm')
542
projectsetid = fields.getfirst('projectsetid').value
543
groupnm = fields.getfirst('groupnm').value
663
544
if projectsetid is None or groupnm is None:
664
545
req.throw_error(req.HTTP_BAD_REQUEST,
665
546
"Required: projectsetid, groupnm")
547
groupnm = unicode(groupnm)
667
549
projectsetid = int(projectsetid)
669
551
req.throw_error(req.HTTP_BAD_REQUEST,
670
552
"projectsetid must be an int")
671
553
# Get optional fields
672
nick = fields.getfirst('nick')
677
createdby = db.get_user_loginid(req.user.login)
678
epoch = time.localtime()
554
nick = fields.getfirst('nick').value
680
558
# Begin transaction since things can go wrong
681
db.start_transaction()
683
dbquery = db.return_insert(
686
'projectsetid': projectsetid,
688
'createdby': createdby,
691
"project_group", # table
692
frozenset(["groupnm", "projectsetid", "nick", "createdby",
694
["groupid"], # returns
697
singlerow = dbquery.dictresult()[0]
698
groupid = singlerow['groupid']
560
group = ivle.database.ProjectGroup(name=groupnm,
561
project_set_id=projectsetid,
564
epoch=datetime.datetime.now())
700
# Create the groups repository
701
# Get the arguments for usermgt.activate_user from the session
702
# (The user must have already logged in to use this app)
704
# Find the rest of the parameters we need
705
offeringinfo = db.get_offering_info(projectsetid)
707
subj_short_name = offeringinfo['subj_short_name']
708
year = offeringinfo['year']
709
semester = offeringinfo['semester']
567
# Create the group repository
568
# Yes, this is ugly, and it would be nice to just pass in the groupid,
569
# but the object isn't visible to the extra transaction in
570
# usrmgt-server until we commit, which we only do once the repo is
572
offering = group.project_set.offering
712
"subj_short_name": subj_short_name,
714
"semester": semester,
575
"subj_short_name": offering.subject.short_name,
576
"year": offering.semester.year,
577
"semester": offering.semester.semester,
578
"groupnm": group.name,
717
580
msg = {'create_group_repository': args}
760
618
req.throw_error(req.HTTP_BAD_REQUEST,
761
619
"groupid must be an int")
620
group = req.store.get(ivle.database.ProjectGroup, groupid)
763
623
offeringid = int(offeringid)
765
625
req.throw_error(req.HTTP_BAD_REQUEST,
766
626
"offeringid must be an int")
770
offeringmembers = db.get_offering_members(offeringid)
771
groupmembers = db.get_projectgroup_members(groupid)
627
offering = req.store.get(ivle.database.Offering, offeringid)
630
offeringmembers = [{'login': user.login,
631
'fullname': user.fullname
632
} for user in offering.members.order_by(
633
ivle.database.User.login)
635
groupmembers = [{'login': user.login,
636
'fullname': user.fullname
637
} for user in group.members.order_by(
638
ivle.database.User.login)
775
641
# Make sure we don't include members in both lists
776
642
for member in groupmembers:
777
643
if member in offeringmembers:
783
649
req.content_type = "text/plain"
784
650
req.write(response)
652
@require_method('POST')
653
@require_cap(caps.CAP_MANAGEGROUPS)
786
654
def handle_assign_group(req, fields):
787
655
""" Required cap: CAP_MANAGEGROUPS
788
656
Assigns a user to a project group
792
if req.method != "POST":
793
req.throw_error(req.HTTP_METHOD_NOT_ALLOWED,
794
"Only POST requests are valid methods to create_user.")
795
# Check if this user is allowed to manage groups
796
if not req.user.hasCap(caps.CAP_MANAGEGROUPS):
797
req.throw_error(req.HTTP_FORBIDDEN,
798
"You do not have permission to manage groups.")
799
660
# Get required fields
800
661
login = fields.getfirst('login')
801
662
groupid = fields.getfirst('groupid')
802
663
if login is None or groupid is None:
803
664
req.throw_error(req.HTTP_BAD_REQUEST,
804
665
"Required: login, groupid")
805
groupid = int(groupid)
810
loginid = db.get_user_loginid(login)
811
except ivle.db.DBException, e:
812
req.throw_error(req.HTTP_BAD_REQUEST, repr(e))
814
# Add assignment to database
815
# We can't use a transaction here, as usrmgt-server needs to see the
823
"group_member", # table
824
frozenset(["loginid", "groupid"]), # fields
667
group = req.store.get(ivle.database.ProjectGroup, int(groupid))
668
user = ivle.database.User.get_by_login(req.store, login)
670
# Add membership to database
671
# We can't keep a transaction open until the end here, as usrmgt-server
672
# needs to see the changes!
674
group.members.add(user)
827
677
# Rebuild the svn config file
828
678
# Contact the usrmgt server