~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/userservice/__init__.py

Merge from new-dispatch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
# Provides an Ajax service for handling user management requests.
23
23
# This includes when a user logs in for the first time.
24
24
 
25
 
# NOTE: This app does NOT require authentication. This is because otherwise it
26
 
# would be blocked from receiving requests to activate when the user is trying
27
 
# to accept the TOS.
28
 
 
29
 
# It must do its own authentication and authorization.
30
 
 
31
25
### Actions ###
32
26
 
33
27
# The one-and-only path segment to userservice determines the action being
154
148
from ivle.auth import AuthError, authenticate
155
149
import urllib
156
150
 
 
151
from ivle.webapp.base.views import BaseView
 
152
from ivle.webapp.base.plugins import ViewPlugin
 
153
from ivle.webapp.errors import NotFound, BadRequest, Unauthorized
 
154
 
157
155
# The user must send this declaration message to ensure they acknowledge the
158
156
# TOS
159
157
USER_DECLARATION = "I accept the IVLE Terms of Service"
166
164
    "svn_pass"
167
165
)
168
166
 
169
 
def handle(req):
170
 
    """Handler for the Console Service AJAX backend application."""
171
 
    if req.user is None and req.path != 'activate_me':
172
 
        # Not logged in.
 
167
class UserServiceView(BaseView):
 
168
    def __init__(self, req, path):
 
169
        if len(path) > 0 and path[-1] == os.sep:
 
170
            self.path = path[:-1]
 
171
        else:
 
172
            self.path = path
 
173
 
 
174
    def authorize(self, req):
173
175
        # XXX: activate_me isn't called by a valid user, so is special for now.
174
 
        req.throw_error(req.HTTP_FORBIDDEN,
175
 
        "You are not logged in to IVLE.")
176
 
    if len(req.path) > 0 and req.path[-1] == os.sep:
177
 
        path = req.path[:-1]
178
 
    else:
179
 
        path = req.path
180
 
    # The path determines which "command" we are receiving
181
 
    fields = req.get_fieldstorage()
182
 
    try:
183
 
        func = actions_map[req.path]
184
 
    except KeyError:
185
 
        req.throw_error(req.HTTP_BAD_REQUEST,
186
 
        "%s is not a valid userservice action." % repr(req.path))
187
 
    func(req, fields)
 
176
        if req.path == 'activate_me' and get_user_details(req) is not None:
 
177
            return True
 
178
        return req.user is not None
 
179
 
 
180
    def render(self, req):
 
181
        # The path determines which "command" we are receiving
 
182
        fields = req.get_fieldstorage()
 
183
        try:
 
184
            func = actions_map[self.path]
 
185
        except KeyError:
 
186
            raise NotFound()
 
187
        func(req, fields)
 
188
 
 
189
class Plugin(ViewPlugin):
 
190
    urls = [
 
191
        ('userservice/*path', UserServiceView)
 
192
    ]
188
193
 
189
194
@require_method('POST')
190
195
def handle_activate_me(req, fields):
206
211
    their acceptance). It must only be called through a POST request.
207
212
    """
208
213
 
209
 
    # XXX: Very special because we don't have a valid user, so req.user is
210
 
    # None.
211
214
    user = get_user_details(req)
212
215
 
213
 
    if user is None:
214
 
        req.throw_error(req.HTTP_FORBIDDEN,
215
 
        "You are not logged in to IVLE.")
216
 
 
217
 
    try:
218
 
        try:
219
 
            declaration = fields.getfirst('declaration')
220
 
        except AttributeError:
221
 
            declaration = None      # Will fail next test
222
 
        if declaration != USER_DECLARATION:
223
 
            req.throw_error(req.HTTP_BAD_REQUEST,
224
 
            "Please use the Terms of Service form instead of talking to "
225
 
            "this service directly.")
226
 
 
227
 
        # Make sure the user's status is "no_agreement", and set status to
228
 
        # pending, within the one transaction. This ensures we only do this
229
 
        # one time.
230
 
        try:
231
 
            # Check that the user's status is "no_agreement".
232
 
            # (Both to avoid redundant calls, and to stop disabled users from
233
 
            # re-enabling their accounts).
234
 
            if user.state != "no_agreement":
235
 
                req.throw_error(req.HTTP_BAD_REQUEST,
236
 
                "You have already agreed to the terms.")
237
 
            # Write state "pending" to ensure we don't try this again
238
 
            user.state = u"pending"
239
 
        except:
240
 
            req.store.rollback()
241
 
            raise
242
 
        req.store.commit()
243
 
 
244
 
        # Get the arguments for usermgt.activate_user from the session
245
 
        # (The user must have already logged in to use this app)
246
 
        args = {
247
 
            "login": user.login,
248
 
        }
249
 
        msg = {'activate_user': args}
250
 
 
251
 
        # Release our lock on the db so usrmgt can write
252
 
        req.store.rollback()
253
 
 
254
 
        # Try and contact the usrmgt server
255
 
        try:
256
 
            response = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
257
 
        except cjson.DecodeError:
258
 
            # Gave back rubbish - set the response to failure
259
 
            response = {'response': 'usrmgt-failure'}
260
 
 
261
 
        # Get the staus of the users request
262
 
        try:
263
 
            status = response['response']
264
 
        except KeyError:
265
 
            status = 'failure'
266
 
        
267
 
        if status == 'okay':
268
 
            user.state = u"enabled"
269
 
        else:
270
 
            # Reset the user back to no agreement
271
 
            user.state = u"no_agreement"
272
 
            req.store.commit()
273
 
 
274
 
        # Write the response
275
 
        req.content_type = "text/plain"
276
 
        req.write(cjson.encode(response))
 
216
    try:
 
217
        declaration = fields.getfirst('declaration')
 
218
    except AttributeError:
 
219
        declaration = None      # Will fail next test
 
220
    if declaration != USER_DECLARATION:
 
221
        raise BadRequest()
 
222
 
 
223
    # Make sure the user's status is "no_agreement", and set status to
 
224
    # pending, within the one transaction. This ensures we only do this
 
225
    # one time.
 
226
    try:
 
227
        # Check that the user's status is "no_agreement".
 
228
        # (Both to avoid redundant calls, and to stop disabled users from
 
229
        # re-enabling their accounts).
 
230
        if user.state != "no_agreement":
 
231
            raise BadRequest("You have already agreed to the terms.")
 
232
        # Write state "pending" to ensure we don't try this again
 
233
        user.state = u"pending"
277
234
    except:
278
235
        req.store.rollback()
279
236
        raise
 
237
    req.store.commit()
 
238
 
 
239
    # Get the arguments for usermgt.activate_user from the session
 
240
    # (The user must have already logged in to use this app)
 
241
    args = {
 
242
        "login": user.login,
 
243
    }
 
244
    msg = {'activate_user': args}
 
245
 
 
246
    # Release our lock on the db so usrmgt can write
 
247
    req.store.rollback()
 
248
 
 
249
    # Try and contact the usrmgt server
 
250
    try:
 
251
        response = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
 
252
    except cjson.DecodeError:
 
253
        # Gave back rubbish - set the response to failure
 
254
        response = {'response': 'usrmgt-failure'}
 
255
 
 
256
    # Get the staus of the users request
 
257
    try:
 
258
        status = response['response']
 
259
    except KeyError:
 
260
        status = 'failure'
 
261
 
 
262
    if status == 'okay':
 
263
        user.state = u"enabled"
 
264
    else:
 
265
        # Reset the user back to no agreement
 
266
        user.state = u"no_agreement"
 
267
 
 
268
    # Write the response
 
269
    req.content_type = "text/plain"
 
270
    req.write(cjson.encode(response))
280
271
 
281
272
create_user_fields_required = [
282
273
    'login', 'fullname', 'rolenm'
320
311
        if val is not None:
321
312
            create[f] = val
322
313
        else:
323
 
            req.throw_error(req.HTTP_BAD_REQUEST,
324
 
            "Required field %s missing." % repr(f))
 
314
            raise BadRequest("Required field %s missing." % repr(f))
325
315
    for f in create_user_fields_optional:
326
316
        val = fields.getfirst(f)
327
317
        if val is not None:
332
322
    user = ivle.database.User(**create)
333
323
    req.store.add(user)
334
324
    ivle.pulldown_subj.enrol_user(req.store, user)
335
 
    req.store.commit()
336
325
 
337
326
    req.content_type = "text/plain"
338
327
    req.write(str(user.unixid))
363
352
            raise AttributeError()
364
353
        if not fullpowers and login != req.user.login:
365
354
            # Not allowed to edit other users
366
 
            req.throw_error(req.HTTP_FORBIDDEN,
367
 
            "You do not have permission to update another user.")
 
355
            raise Unauthorized()
368
356
    except AttributeError:
369
357
        # If login not specified, update yourself
370
358
        login = req.user.login
381
369
        try:
382
370
            authenticate.authenticate(req.store, login, oldpassword)
383
371
        except AuthError:
 
372
            # XXX: Duplicated!
384
373
            req.headers_out['X-IVLE-Action-Error'] = \
385
374
                urllib.quote("Old password incorrect.")
386
 
            req.status = req.HTTP_BAD_REQUEST
387
 
            # Cancel all the changes made to user (including setting new pass)
388
 
            req.store.rollback()
389
 
            return
 
375
            raise BadRequest("Old password incorrect.")
390
376
 
391
377
    # Make a dict of fields to update
392
378
    for f in fieldlist:
397
383
        else:
398
384
            pass
399
385
 
400
 
    req.store.commit()
401
 
 
402
386
    req.content_type = "text/plain"
403
387
    req.write('')
404
388
 
416
400
        if login is None:
417
401
            raise AttributeError()
418
402
        if not fullpowers and login != req.user.login:
419
 
            # Not allowed to edit other users
420
 
            req.throw_error(req.HTTP_FORBIDDEN,
421
 
            "You do not have permission to see another user.")
 
403
            raise Unauthorized()
422
404
    except AttributeError:
423
405
        # If login not specified, update yourself
424
406
        login = req.user.login
453
435
        if user is None:
454
436
            raise AttributeError()
455
437
        if not fullpowers and user != req.user:
456
 
            # Not allowed to edit other users
457
 
            req.throw_error(req.HTTP_FORBIDDEN,
458
 
            "You do not have permission to see another user's subjects.")
 
438
            raise Unauthorized()
459
439
    except AttributeError:
460
440
        # If login not specified, update yourself
461
441
        user = req.user
486
466
 
487
467
    subjectid = fields.getfirst('subjectid')
488
468
    if subjectid is None:
489
 
        req.throw_error(req.HTTP_BAD_REQUEST,
490
 
            "Required: subjectid")
 
469
        raise BadRequest("Required: subjectid")
491
470
    try:
492
471
        subjectid = int(subjectid)
493
472
    except:
494
 
        req.throw_error(req.HTTP_BAD_REQUEST,
495
 
            "subjectid must be a integer")
 
473
        raise BadRequest("subjectid must be an integer")
496
474
 
497
475
    subject = req.store.get(ivle.database.Subject, subjectid)
498
476
 
515
493
 
516
494
    offeringid = fields.getfirst('offeringid')
517
495
    if offeringid is None:
518
 
        req.throw_error(req.HTTP_BAD_REQUEST,
519
 
            "Required: offeringid")
 
496
        raise BadRequest("Required: offeringid")
520
497
    try:
521
498
        offeringid = int(offeringid)
522
499
    except:
523
 
        req.throw_error(req.HTTP_BAD_REQUEST,
524
 
            "offeringid must be a integer")
 
500
        raise BadRequest("offeringid must be an integer")
525
501
 
526
502
    offering = req.store.get(ivle.database.Offering, offeringid)
527
503
 
528
504
    dict_projectsets = []
529
 
    try:
530
 
        for p in offering.project_sets:
531
 
            dict_projectsets.append({
532
 
                'projectsetid': p.id,
533
 
                'max_students_per_group': p.max_students_per_group,
534
 
                'groups': [{'groupid': g.id,
535
 
                            'groupnm': g.name,
536
 
                            'nick': g.nick} for g in p.project_groups]
537
 
            })
538
 
    except Exception, e:
539
 
        req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR, repr(e))
 
505
    for p in offering.project_sets:
 
506
        dict_projectsets.append({
 
507
            'projectsetid': p.id,
 
508
            'max_students_per_group': p.max_students_per_group,
 
509
            'groups': [{'groupid': g.id,
 
510
                        'groupnm': g.name,
 
511
                        'nick': g.nick} for g in p.project_groups]
 
512
        })
540
513
 
541
514
    response = cjson.encode(dict_projectsets)
542
515
    req.write(response)
557
530
    projectsetid = fields.getfirst('projectsetid').value
558
531
    groupnm = fields.getfirst('groupnm').value
559
532
    if projectsetid is None or groupnm is None:
560
 
        req.throw_error(req.HTTP_BAD_REQUEST,
561
 
            "Required: projectsetid, groupnm")
 
533
        raise BadRequest("Required: projectsetid, groupnm")
562
534
    groupnm = unicode(groupnm)
563
535
    try:
564
536
        projectsetid = int(projectsetid)
565
537
    except:
566
 
        req.throw_error(req.HTTP_BAD_REQUEST,
567
 
            "projectsetid must be an int")
 
538
        raise BadRequest("projectsetid must be an integer")
 
539
 
568
540
    # Get optional fields
569
541
    nick = fields.getfirst('nick').value
570
542
    if nick is not None:
571
543
        nick = unicode(nick)
572
544
 
573
 
    # Begin transaction since things can go wrong
 
545
    group = ivle.database.ProjectGroup(name=groupnm,
 
546
                                       project_set_id=projectsetid,
 
547
                                       nick=nick,
 
548
                                       created_by=req.user,
 
549
                                       epoch=datetime.datetime.now())
 
550
    req.store.add(group)
 
551
 
 
552
    # Create the group repository
 
553
    # Yes, this is ugly, and it would be nice to just pass in the groupid,
 
554
    # but the object isn't visible to the extra transaction in
 
555
    # usrmgt-server until we commit, which we only do once the repo is
 
556
    # created.
 
557
    offering = group.project_set.offering
 
558
 
 
559
    args = {
 
560
        "subj_short_name": offering.subject.short_name,
 
561
        "year": offering.semester.year,
 
562
        "semester": offering.semester.semester,
 
563
        "groupnm": group.name,
 
564
    }
 
565
    msg = {'create_group_repository': args}
 
566
 
 
567
    # Contact the usrmgt server
574
568
    try:
575
 
        group = ivle.database.ProjectGroup(name=groupnm,
576
 
                                           project_set_id=projectsetid,
577
 
                                           nick=nick,
578
 
                                           created_by=req.user,
579
 
                                           epoch=datetime.datetime.now())
580
 
        req.store.add(group)
581
 
 
582
 
        # Create the group repository
583
 
        # Yes, this is ugly, and it would be nice to just pass in the groupid,
584
 
        # but the object isn't visible to the extra transaction in
585
 
        # usrmgt-server until we commit, which we only do once the repo is
586
 
        # created.
587
 
        offering = group.project_set.offering
588
 
 
589
 
        args = {
590
 
            "subj_short_name": offering.subject.short_name,
591
 
            "year": offering.semester.year,
592
 
            "semester": offering.semester.semester,
593
 
            "groupnm": group.name,
594
 
        }
595
 
        msg = {'create_group_repository': args}
596
 
 
597
 
        # Contact the usrmgt server
598
 
        try:
599
 
            usrmgt = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
600
 
        except cjson.DecodeError, e:
601
 
            req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
602
 
                "Could not understand usrmgt server response: %s"%e.message)
603
 
 
604
 
        if 'response' not in usrmgt or usrmgt['response']=='failure':
605
 
            req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
606
 
                "Failure creating repository: %s"%str(usrmgt))
607
 
    
608
 
        # Everything went OK. Lock it in
609
 
        req.store.commit()
610
 
 
611
 
    except Exception, e:
612
 
        req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR, repr(e))
 
569
        usrmgt = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
 
570
    except cjson.DecodeError, e:
 
571
        raise Exception("Could not understand usrmgt server response:" +
 
572
                        e.message)
 
573
 
 
574
    if 'response' not in usrmgt or usrmgt['response']=='failure':
 
575
        raise Exception("Failure creating repository: " + str(usrmgt))
613
576
 
614
577
    req.content_type = "text/plain"
615
578
    req.write('')
625
588
    groupid = fields.getfirst('groupid')
626
589
    offeringid = fields.getfirst('offeringid')
627
590
    if groupid is None or offeringid is None:
628
 
        req.throw_error(req.HTTP_BAD_REQUEST,
629
 
            "Required: groupid, offeringid")
 
591
        raise BadRequest("Required: groupid, offeringid")
630
592
    try:
631
593
        groupid = int(groupid)
632
594
    except:
633
 
        req.throw_error(req.HTTP_BAD_REQUEST,
634
 
            "groupid must be an int")
 
595
        raise BadRequest("groupid must be an integer")
635
596
    group = req.store.get(ivle.database.ProjectGroup, groupid)
636
597
 
637
598
    try:
638
599
        offeringid = int(offeringid)
639
600
    except:
640
 
        req.throw_error(req.HTTP_BAD_REQUEST,
641
 
            "offeringid must be an int")
 
601
        raise BadRequest("offeringid must be an integer")
642
602
    offering = req.store.get(ivle.database.Offering, offeringid)
643
603
 
644
604
 
676
636
    login = fields.getfirst('login')
677
637
    groupid = fields.getfirst('groupid')
678
638
    if login is None or groupid is None:
679
 
        req.throw_error(req.HTTP_BAD_REQUEST,
680
 
            "Required: login, groupid")
 
639
        raise BadRequest("Required: login, groupid")
681
640
 
682
641
    group = req.store.get(ivle.database.ProjectGroup, int(groupid))
683
642
    user = ivle.database.User.get_by_login(req.store, login)
685
644
    # Add membership to database
686
645
    # We can't keep a transaction open until the end here, as usrmgt-server
687
646
    # needs to see the changes!
 
647
    group.members.add(user)
 
648
    req.store.commit()
 
649
 
 
650
    # Rebuild the svn config file
 
651
    # Contact the usrmgt server
 
652
    msg = {'rebuild_svn_group_config': {}}
688
653
    try:
689
 
        group.members.add(user)
690
 
        req.store.commit()
691
 
 
692
 
        # Rebuild the svn config file
693
 
        # Contact the usrmgt server
694
 
        msg = {'rebuild_svn_group_config': {}}
695
 
        try:
696
 
            usrmgt = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
697
 
        except cjson.DecodeError, e:
698
 
            req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
699
 
                "Could not understand usrmgt server response: %s"%e.message)
700
 
 
701
 
            if 'response' not in usrmgt or usrmgt['response']=='failure':
702
 
                req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
703
 
                    "Failure creating repository: %s"%str(usrmgt))
704
 
    except Exception, e:
705
 
        req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR, repr(e))
 
654
        usrmgt = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
 
655
    except cjson.DecodeError, e:
 
656
        raise Exception("Could not understand usrmgt server response: %s" +
 
657
                        e.message)
 
658
 
 
659
        if 'response' not in usrmgt or usrmgt['response']=='failure':
 
660
            raise Exception("Failure creating repository: " + str(usrmgt))
706
661
 
707
662
    return(cjson.encode({'response': 'okay'}))
708
663