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

« back to all changes in this revision

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

  • Committer: William Grant
  • Date: 2009-02-23 23:47:02 UTC
  • mfrom: (1099.1.211 new-dispatch)
  • Revision ID: grantw@unimelb.edu.au-20090223234702-db4b1llly46ignwo
Merge from lp:~ivle-dev/ivle/new-dispatch.

Pretty much everything changes. Reread the setup docs. Backup your databases.
Every file is now in a different installed location, the configuration system
is rewritten, the dispatch system is rewritten, URLs are different, the
database is different, worksheets and exercises are no longer on the
filesystem, we use a templating engine, jail service protocols are rewritten,
we don't repeat ourselves, we have authorization rewritten, phpBB is gone,
and probably lots of other things that I cannot remember.

This is certainly the biggest commit I have ever made, and hopefully
the largest I ever will.

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
142
136
import datetime
143
137
 
144
138
import cjson
145
 
import pg
146
139
 
147
140
import ivle.database
148
141
from ivle import (util, chat, caps)
149
142
from ivle.conf import (usrmgt_host, usrmgt_port, usrmgt_magic)
 
143
from ivle.webapp.security import get_user_details
150
144
import ivle.pulldown_subj
151
145
 
152
146
from ivle.rpc.decorators import require_method, require_cap
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:
172
 
        # Not logged in
173
 
        req.throw_error(req.HTTP_FORBIDDEN,
174
 
        "You are not logged in to IVLE.")
175
 
    if len(req.path) > 0 and req.path[-1] == os.sep:
176
 
        path = req.path[:-1]
177
 
    else:
178
 
        path = req.path
179
 
    # The path determines which "command" we are receiving
180
 
    fields = req.get_fieldstorage()
181
 
    try:
182
 
        func = actions_map[req.path]
183
 
    except KeyError:
184
 
        req.throw_error(req.HTTP_BAD_REQUEST,
185
 
        "%s is not a valid userservice action." % repr(req.path))
186
 
    func(req, fields)
 
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):
 
175
        # XXX: activate_me isn't called by a valid user, so is special for now.
 
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
    ]
187
193
 
188
194
@require_method('POST')
189
195
def handle_activate_me(req, fields):
204
210
    "accepting" the terms - at least this way requires them to acknowledge
205
211
    their acceptance). It must only be called through a POST request.
206
212
    """
207
 
    try:
208
 
        try:
209
 
            declaration = fields.getfirst('declaration')
210
 
        except AttributeError:
211
 
            declaration = None      # Will fail next test
212
 
        if declaration != USER_DECLARATION:
213
 
            req.throw_error(req.HTTP_BAD_REQUEST,
214
 
            "Please use the Terms of Service form instead of talking to "
215
 
            "this service directly.")
216
 
 
217
 
        # Make sure the user's status is "no_agreement", and set status to
218
 
        # pending, within the one transaction. This ensures we only do this
219
 
        # one time.
220
 
        try:
221
 
            # Check that the user's status is "no_agreement".
222
 
            # (Both to avoid redundant calls, and to stop disabled users from
223
 
            # re-enabling their accounts).
224
 
            if req.user.state != "no_agreement":
225
 
                req.throw_error(req.HTTP_BAD_REQUEST,
226
 
                "You have already agreed to the terms.")
227
 
            # Write state "pending" to ensure we don't try this again
228
 
            req.user.state = u"pending"
229
 
        except:
230
 
            req.store.rollback()
231
 
            raise
232
 
        req.store.commit()
233
 
 
234
 
        # Get the arguments for usermgt.activate_user from the session
235
 
        # (The user must have already logged in to use this app)
236
 
        args = {
237
 
            "login": req.user.login,
238
 
        }
239
 
        msg = {'activate_user': args}
240
 
 
241
 
        # Release our lock on the db so usrmgt can write
242
 
        req.store.rollback()
243
 
 
244
 
        # Try and contact the usrmgt server
245
 
        try:
246
 
            response = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
247
 
        except cjson.DecodeError:
248
 
            # Gave back rubbish - set the response to failure
249
 
            response = {'response': 'usrmgt-failure'}
250
 
 
251
 
        # Get the staus of the users request
252
 
        try:
253
 
            status = response['response']
254
 
        except KeyError:
255
 
            status = 'failure'
256
 
        
257
 
        if status == 'okay':
258
 
            req.user.state = u"enabled"
259
 
        else:
260
 
            # Reset the user back to no agreement
261
 
            req.user.state = u"no_agreement"
262
 
            req.store.commit()
263
 
 
264
 
        # Write the response
265
 
        req.content_type = "text/plain"
266
 
        req.write(cjson.encode(response))
 
213
 
 
214
    user = get_user_details(req)
 
215
 
 
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"
267
234
    except:
268
235
        req.store.rollback()
269
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))
270
271
 
271
272
create_user_fields_required = [
272
273
    'login', 'fullname', 'rolenm'
310
311
        if val is not None:
311
312
            create[f] = val
312
313
        else:
313
 
            req.throw_error(req.HTTP_BAD_REQUEST,
314
 
            "Required field %s missing." % repr(f))
 
314
            raise BadRequest("Required field %s missing." % repr(f))
315
315
    for f in create_user_fields_optional:
316
316
        val = fields.getfirst(f)
317
317
        if val is not None:
322
322
    user = ivle.database.User(**create)
323
323
    req.store.add(user)
324
324
    ivle.pulldown_subj.enrol_user(req.store, user)
325
 
    req.store.commit()
326
325
 
327
326
    req.content_type = "text/plain"
328
327
    req.write(str(user.unixid))
353
352
            raise AttributeError()
354
353
        if not fullpowers and login != req.user.login:
355
354
            # Not allowed to edit other users
356
 
            req.throw_error(req.HTTP_FORBIDDEN,
357
 
            "You do not have permission to update another user.")
 
355
            raise Unauthorized()
358
356
    except AttributeError:
359
357
        # If login not specified, update yourself
360
358
        login = req.user.login
371
369
        try:
372
370
            authenticate.authenticate(req.store, login, oldpassword)
373
371
        except AuthError:
 
372
            # XXX: Duplicated!
374
373
            req.headers_out['X-IVLE-Action-Error'] = \
375
374
                urllib.quote("Old password incorrect.")
376
 
            req.status = req.HTTP_BAD_REQUEST
377
 
            # Cancel all the changes made to user (including setting new pass)
378
 
            req.store.rollback()
379
 
            return
 
375
            raise BadRequest("Old password incorrect.")
380
376
 
381
377
    # Make a dict of fields to update
382
378
    for f in fieldlist:
387
383
        else:
388
384
            pass
389
385
 
390
 
    req.store.commit()
391
 
 
392
386
    req.content_type = "text/plain"
393
387
    req.write('')
394
388
 
406
400
        if login is None:
407
401
            raise AttributeError()
408
402
        if not fullpowers and login != req.user.login:
409
 
            # Not allowed to edit other users
410
 
            req.throw_error(req.HTTP_FORBIDDEN,
411
 
            "You do not have permission to see another user.")
 
403
            raise Unauthorized()
412
404
    except AttributeError:
413
405
        # If login not specified, update yourself
414
406
        login = req.user.login
443
435
        if user is None:
444
436
            raise AttributeError()
445
437
        if not fullpowers and user != req.user:
446
 
            # Not allowed to edit other users
447
 
            req.throw_error(req.HTTP_FORBIDDEN,
448
 
            "You do not have permission to see another user's subjects.")
 
438
            raise Unauthorized()
449
439
    except AttributeError:
450
440
        # If login not specified, update yourself
451
441
        user = req.user
476
466
 
477
467
    subjectid = fields.getfirst('subjectid')
478
468
    if subjectid is None:
479
 
        req.throw_error(req.HTTP_BAD_REQUEST,
480
 
            "Required: subjectid")
 
469
        raise BadRequest("Required: subjectid")
481
470
    try:
482
471
        subjectid = int(subjectid)
483
472
    except:
484
 
        req.throw_error(req.HTTP_BAD_REQUEST,
485
 
            "subjectid must be a integer")
 
473
        raise BadRequest("subjectid must be an integer")
486
474
 
487
475
    subject = req.store.get(ivle.database.Subject, subjectid)
488
476
 
505
493
 
506
494
    offeringid = fields.getfirst('offeringid')
507
495
    if offeringid is None:
508
 
        req.throw_error(req.HTTP_BAD_REQUEST,
509
 
            "Required: offeringid")
 
496
        raise BadRequest("Required: offeringid")
510
497
    try:
511
498
        offeringid = int(offeringid)
512
499
    except:
513
 
        req.throw_error(req.HTTP_BAD_REQUEST,
514
 
            "offeringid must be a integer")
 
500
        raise BadRequest("offeringid must be an integer")
515
501
 
516
502
    offering = req.store.get(ivle.database.Offering, offeringid)
517
503
 
518
504
    dict_projectsets = []
519
 
    try:
520
 
        for p in offering.project_sets:
521
 
            dict_projectsets.append({
522
 
                'projectsetid': p.id,
523
 
                'max_students_per_group': p.max_students_per_group,
524
 
                'groups': [{'groupid': g.id,
525
 
                            'groupnm': g.name,
526
 
                            'nick': g.nick} for g in p.project_groups]
527
 
            })
528
 
    except Exception, e:
529
 
        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
        })
530
513
 
531
514
    response = cjson.encode(dict_projectsets)
532
515
    req.write(response)
547
530
    projectsetid = fields.getfirst('projectsetid').value
548
531
    groupnm = fields.getfirst('groupnm').value
549
532
    if projectsetid is None or groupnm is None:
550
 
        req.throw_error(req.HTTP_BAD_REQUEST,
551
 
            "Required: projectsetid, groupnm")
 
533
        raise BadRequest("Required: projectsetid, groupnm")
552
534
    groupnm = unicode(groupnm)
553
535
    try:
554
536
        projectsetid = int(projectsetid)
555
537
    except:
556
 
        req.throw_error(req.HTTP_BAD_REQUEST,
557
 
            "projectsetid must be an int")
 
538
        raise BadRequest("projectsetid must be an integer")
 
539
 
558
540
    # Get optional fields
559
541
    nick = fields.getfirst('nick').value
560
542
    if nick is not None:
561
543
        nick = unicode(nick)
562
544
 
563
 
    # 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
564
568
    try:
565
 
        group = ivle.database.ProjectGroup(name=groupnm,
566
 
                                           project_set_id=projectsetid,
567
 
                                           nick=nick,
568
 
                                           created_by=req.user,
569
 
                                           epoch=datetime.datetime.now())
570
 
        req.store.add(group)
571
 
 
572
 
        # Create the group repository
573
 
        # Yes, this is ugly, and it would be nice to just pass in the groupid,
574
 
        # but the object isn't visible to the extra transaction in
575
 
        # usrmgt-server until we commit, which we only do once the repo is
576
 
        # created.
577
 
        offering = group.project_set.offering
578
 
 
579
 
        args = {
580
 
            "subj_short_name": offering.subject.short_name,
581
 
            "year": offering.semester.year,
582
 
            "semester": offering.semester.semester,
583
 
            "groupnm": group.name,
584
 
        }
585
 
        msg = {'create_group_repository': args}
586
 
 
587
 
        # Contact the usrmgt server
588
 
        try:
589
 
            usrmgt = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
590
 
        except cjson.DecodeError, e:
591
 
            req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
592
 
                "Could not understand usrmgt server response: %s"%e.message)
593
 
 
594
 
        if 'response' not in usrmgt or usrmgt['response']=='failure':
595
 
            req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
596
 
                "Failure creating repository: %s"%str(usrmgt))
597
 
    
598
 
        # Everything went OK. Lock it in
599
 
        req.store.commit()
600
 
 
601
 
    except Exception, e:
602
 
        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))
603
576
 
604
577
    req.content_type = "text/plain"
605
578
    req.write('')
615
588
    groupid = fields.getfirst('groupid')
616
589
    offeringid = fields.getfirst('offeringid')
617
590
    if groupid is None or offeringid is None:
618
 
        req.throw_error(req.HTTP_BAD_REQUEST,
619
 
            "Required: groupid, offeringid")
 
591
        raise BadRequest("Required: groupid, offeringid")
620
592
    try:
621
593
        groupid = int(groupid)
622
594
    except:
623
 
        req.throw_error(req.HTTP_BAD_REQUEST,
624
 
            "groupid must be an int")
 
595
        raise BadRequest("groupid must be an integer")
625
596
    group = req.store.get(ivle.database.ProjectGroup, groupid)
626
597
 
627
598
    try:
628
599
        offeringid = int(offeringid)
629
600
    except:
630
 
        req.throw_error(req.HTTP_BAD_REQUEST,
631
 
            "offeringid must be an int")
 
601
        raise BadRequest("offeringid must be an integer")
632
602
    offering = req.store.get(ivle.database.Offering, offeringid)
633
603
 
634
604
 
666
636
    login = fields.getfirst('login')
667
637
    groupid = fields.getfirst('groupid')
668
638
    if login is None or groupid is None:
669
 
        req.throw_error(req.HTTP_BAD_REQUEST,
670
 
            "Required: login, groupid")
 
639
        raise BadRequest("Required: login, groupid")
671
640
 
672
641
    group = req.store.get(ivle.database.ProjectGroup, int(groupid))
673
642
    user = ivle.database.User.get_by_login(req.store, login)
675
644
    # Add membership to database
676
645
    # We can't keep a transaction open until the end here, as usrmgt-server
677
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': {}}
678
653
    try:
679
 
        group.members.add(user)
680
 
        req.store.commit()
681
 
 
682
 
        # Rebuild the svn config file
683
 
        # Contact the usrmgt server
684
 
        msg = {'rebuild_svn_group_config': {}}
685
 
        try:
686
 
            usrmgt = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic)
687
 
        except cjson.DecodeError, e:
688
 
            req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
689
 
                "Could not understand usrmgt server response: %s"%e.message)
690
 
 
691
 
            if 'response' not in usrmgt or usrmgt['response']=='failure':
692
 
                req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
693
 
                    "Failure creating repository: %s"%str(usrmgt))
694
 
    except Exception, e:
695
 
        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))
696
661
 
697
662
    return(cjson.encode({'response': 'okay'}))
698
663