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

465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
1
# IVLE
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18
# App: userservice
19
# Author: Matt Giuca
20
# Date: 14/2/2008
21
478 by mattgiuca
scripts/usrmgr-server: Renamed actions from dashes to underscores.
22
# Provides an Ajax service for handling user management requests.
23
# This includes when a user logs in for the first time.
24
486 by mattgiuca
caps: Added a few new capabilities.
25
### Actions ###
26
27
# The one-and-only path segment to userservice determines the action being
28
# undertaken.
29
# All actions require that you are logged in.
30
# All actions require method = POST, unless otherwise stated.
31
32
# userservice/activate_me
33
# Required cap: None
34
# declaration = "I accept the IVLE Terms of Service"
35
# Activate the currently-logged-in user's account. Requires that "declaration"
36
# is as above, and that the user's state is "no_agreement".
37
38
# userservice/create_user
39
# Required cap: CAP_CREATEUSER
40
# Arguments are the same as the database columns for the "login" table.
41
# Required:
42
#   login, fullname, rolenm
43
# Optional:
44
#   password, nick, email, studentid
45
46
# userservice/get_user
47
# method: May be GET
48
# Required cap: None to see yourself.
49
#   CAP_GETUSER to see another user.
50
# Gets the login details of a user. Returns as a JSON object.
51
# login = Optional login name of user to get. If omitted, get yourself.
52
53
# userservice/update_user
54
# Required cap: None to update yourself.
55
#   CAP_UPDATEUSER to update another user (and also more fields).
56
#   (This is all-powerful so should be only for admins)
57
# login = Optional login name of user to update. If omitted, update yourself.
58
# Other fields are optional, and will set the given field of the user.
59
# Without CAP_UPDATEUSER, you may change the following fields of yourself:
60
#   password, nick, email
61
# With CAP_UPDATEUSER, you may also change the following fields of any user:
62
#   password, nick, email, login, rolenm, unixid, fullname, studentid
63
# (You can't change "state", but see userservice/[en|dis]able_user).
64
65
# TODO
66
# userservice/enable_user
67
# Required cap: CAP_UPDATEUSER
68
# Enable a user whose account has been disabled. Does not work for
69
# no_agreement or pending users.
70
# login = Login name of user to enable.
71
72
# TODO
73
# userservice/disable_user
74
# Required cap: CAP_UPDATEUSER
75
# Disable a user's account. Does not work for no_agreement or pending users.
76
# login = Login name of user to disable.
77
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
78
# userservice/get_enrolments
79
# Required cap: None (for yourself)
80
# Returns a JSON encoded listing of a students is enrollments
81
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
82
# userservice/get_active_offerings(req, fields):
83
# Required cap: None
84
# Returns all the active offerings for a particular subject
85
# Required:
86
#   subjectid
87
981 by dcoles
Groups: Added in support for creating groups in the database through
88
# PROJECTS AND GROUPS
89
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
90
# userservice/get_project_groups
91
# Required cap: None
92
# Returns all the project groups in an offering grouped by project set
93
# Required:
94
#   offeringid
95
981 by dcoles
Groups: Added in support for creating groups in the database through
96
# userservice/create_project_set
97
# Required cap: CAP_MANAGEPROJECTS
984 by dcoles
Groups: Added userservice/create_project call. You can now create a project in
98
# Creates a project set for a offering
981 by dcoles
Groups: Added in support for creating groups in the database through
99
# Required:
100
#   offeringid, max_students_per_group
984 by dcoles
Groups: Added userservice/create_project call. You can now create a project in
101
# Returns:
102
#   projectsetid
981 by dcoles
Groups: Added in support for creating groups in the database through
103
104
# userservice/create_project
105
# Required cap: CAP_MANAGEPROJECTS
106
# Creates a project in a specific project set
107
# Required:
108
#   projectsetid
109
# Optional:
984 by dcoles
Groups: Added userservice/create_project call. You can now create a project in
110
#   synopsis, url, deadline
111
# Returns:
112
#   projectid
981 by dcoles
Groups: Added in support for creating groups in the database through
113
114
# userservice/create_group
115
# Required cap: CAP_MANAGEGROUPS
116
# Creates a project group in a specific project set
117
# Required:
118
#   projectsetid, groupnm
119
# Optional:
120
#   nick
121
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
122
# userservice/get_group_membership
123
# Required cap: None
124
# Returns two lists. One of people in the group and one of people not in the 
125
# group (but enroled in the offering)
126
# Required:
127
#   groupid
128
981 by dcoles
Groups: Added in support for creating groups in the database through
129
# userservice/assign_to_group
130
# Required cap: CAP_MANAGEGROUPS
131
# Assigns a user to a project group
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
132
# Required: login, groupid
981 by dcoles
Groups: Added in support for creating groups in the database through
133
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
134
import os
135
import sys
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
136
import datetime
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
137
138
import cjson
139
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
140
import ivle.database
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
141
from ivle import (util, chat, caps)
142
from ivle.conf import (usrmgt_host, usrmgt_port, usrmgt_magic)
1099.1.161 by William Grant
Move ivle.dispatch.login.get_user_details() to ivle.webapp.security.
143
from ivle.webapp.security import get_user_details
1080.1.73 by William Grant
www/apps/userservice: create_user now creates and enrols the User itself, not
144
import ivle.pulldown_subj
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
145
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
146
from ivle.rpc.decorators import require_method, require_cap
147
1080.1.12 by me at id
ivle.auth.autherror: Remove, moving AuthError into ivle.auth itself.
148
from ivle.auth import AuthError, authenticate
1078 by chadnickbok
Updated the settings page to require the old password
149
import urllib
150
1099.1.132 by William Grant
Direct port of userservice to the new framework.
151
from ivle.webapp.base.views import BaseView
152
from ivle.webapp.base.plugins import ViewPlugin
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
153
from ivle.webapp.errors import NotFound, BadRequest, Unauthorized
1099.1.132 by William Grant
Direct port of userservice to the new framework.
154
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
155
# The user must send this declaration message to ensure they acknowledge the
156
# TOS
157
USER_DECLARATION = "I accept the IVLE Terms of Service"
158
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
159
# List of fields returned as part of the user JSON dictionary
160
# (as returned by the get_user action)
161
user_fields_list = (
162
    "login", "state", "unixid", "email", "nick", "fullname",
163
    "rolenm", "studentid", "acct_exp", "pass_exp", "last_login",
164
    "svn_pass"
165
)
166
1099.1.132 by William Grant
Direct port of userservice to the new framework.
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):
1099.1.127 by William Grant
Implement ToS acceptance in the new login machinery. Now implemented through
175
        # XXX: activate_me isn't called by a valid user, so is special for now.
1099.1.132 by William Grant
Direct port of userservice to the new framework.
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
    ]
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
193
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
194
@require_method('POST')
486 by mattgiuca
caps: Added a few new capabilities.
195
def handle_activate_me(req, fields):
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
196
    """Create the jail, svn, etc, for the currently logged in user (this is
197
    put in the queue for usermgt to do).
198
    This will block until usermgt returns, which could take seconds to minutes
199
    in the extreme. Therefore, it is designed to be called by Ajax, with a
200
    nice "Please wait" message on the frontend.
201
202
    This will signal that the user has accepted the terms of the license
203
    agreement, and will result in the user's database status being set to
204
    "enabled". (Note that it will be set to "pending" for the duration of the
205
    handling).
206
207
    As such, it takes a single POST field, "declaration", which
208
    must have the value, "I accept the IVLE Terms of Service".
209
    (Otherwise users could navigate to /userservice/createme without
210
    "accepting" the terms - at least this way requires them to acknowledge
211
    their acceptance). It must only be called through a POST request.
212
    """
1099.1.127 by William Grant
Implement ToS acceptance in the new login machinery. Now implemented through
213
1099.1.137 by William Grant
Actually set user in userservice/activate_me.
214
    user = get_user_details(req)
215
486 by mattgiuca
caps: Added a few new capabilities.
216
    try:
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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"
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
234
    except:
235
        req.store.rollback()
236
        raise
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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))
486 by mattgiuca
caps: Added a few new capabilities.
271
272
create_user_fields_required = [
273
    'login', 'fullname', 'rolenm'
274
]
275
create_user_fields_optional = [
943 by wagrant
makeuser, userservice: Don't allow specification of a custom unixid, as
276
    'password', 'nick', 'email', 'studentid'
486 by mattgiuca
caps: Added a few new capabilities.
277
]
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
278
279
@require_method('POST')
280
@require_cap(caps.CAP_UPDATEUSER)
486 by mattgiuca
caps: Added a few new capabilities.
281
def handle_create_user(req, fields):
282
    """Create a new user, whose state is no_agreement.
283
    This does not create the user's jail, just an entry in the database which
284
    allows the user to accept an agreement.
940 by wagrant
userservice: Repeat after me: I will not talk to code that runs as root
285
       Expected fields:
286
        login       - used as a unix login name and svn repository name.
287
                      STRING REQUIRED 
288
        password    - the clear-text password for the user. If this property is
289
                      absent or None, this is an indication that external
290
                      authentication should be used (i.e. LDAP).
291
                      STRING OPTIONAL
292
        email       - the user's email address.
293
                      STRING OPTIONAL
294
        nick        - the display name to use.
295
                      STRING REQUIRED
296
        fullname    - The name of the user for results and/or other official
297
                      purposes.
298
                      STRING REQUIRED
299
        rolenm      - The user's role. Must be one of "anyone", "student",
300
                      "tutor", "lecturer", "admin".
301
                      STRING/ENUM REQUIRED
302
        studentid   - If supplied and not None, the student id of the user for
303
                      results and/or other official purposes.
304
                      STRING OPTIONAL
305
       Return Value: the uid associated with the user. INT
486 by mattgiuca
caps: Added a few new capabilities.
306
    """
307
    # Make a dict of fields to create
308
    create = {}
309
    for f in create_user_fields_required:
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
310
        val = fields.getfirst(f)
311
        if val is not None:
312
            create[f] = val
313
        else:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
314
            raise BadRequest("Required field %s missing." % repr(f))
486 by mattgiuca
caps: Added a few new capabilities.
315
    for f in create_user_fields_optional:
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
316
        val = fields.getfirst(f)
317
        if val is not None:
318
            create[f] = val
319
        else:
486 by mattgiuca
caps: Added a few new capabilities.
320
            pass
321
1080.1.73 by William Grant
www/apps/userservice: create_user now creates and enrols the User itself, not
322
    user = ivle.database.User(**create)
323
    req.store.add(user)
324
    ivle.pulldown_subj.enrol_user(req.store, user)
1080.1.21 by me at id
userservice/create_user: Use storm rather than ivle.db.get_user.
325
486 by mattgiuca
caps: Added a few new capabilities.
326
    req.content_type = "text/plain"
1080.1.73 by William Grant
www/apps/userservice: create_user now creates and enrols the User itself, not
327
    req.write(str(user.unixid))
486 by mattgiuca
caps: Added a few new capabilities.
328
329
update_user_fields_anyone = [
330
    'password', 'nick', 'email'
331
]
332
update_user_fields_admin = [
552 by mattgiuca
userservice: Small fixes to update_user.
333
    'password', 'nick', 'email', 'rolenm', 'unixid', 'fullname',
486 by mattgiuca
caps: Added a few new capabilities.
334
    'studentid'
335
]
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
336
337
@require_method('POST')
486 by mattgiuca
caps: Added a few new capabilities.
338
def handle_update_user(req, fields):
339
    """Update a user's account details.
340
    This can be done in a limited form by any user, on their own account,
341
    or with full powers by a user with CAP_UPDATEUSER on any account.
342
    """
343
    # Only give full powers if this user has CAP_UPDATEUSER
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
344
    fullpowers = req.user.hasCap(caps.CAP_UPDATEUSER)
486 by mattgiuca
caps: Added a few new capabilities.
345
    # List of fields that may be changed
346
    fieldlist = (update_user_fields_admin if fullpowers
347
        else update_user_fields_anyone)
348
349
    try:
350
        login = fields.getfirst('login')
552 by mattgiuca
userservice: Small fixes to update_user.
351
        if login is None:
352
            raise AttributeError()
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
353
        if not fullpowers and login != req.user.login:
486 by mattgiuca
caps: Added a few new capabilities.
354
            # Not allowed to edit other users
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
355
            raise Unauthorized()
486 by mattgiuca
caps: Added a few new capabilities.
356
    except AttributeError:
357
        # If login not specified, update yourself
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
358
        login = req.user.login
486 by mattgiuca
caps: Added a few new capabilities.
359
1080.1.20 by me at id
userservice/update_user: Only get the user once, not for each attr.
360
    user = ivle.database.User.get_by_login(req.store, login)
361
1078 by chadnickbok
Updated the settings page to require the old password
362
    oldpassword = fields.getfirst('oldpass')
1094 by me at id
www/apps/userservice#get_user: Fix fallout from the Storm migration.
363
    if oldpassword is not None: # It was specified.
364
        oldpassword = oldpassword.value
1078 by chadnickbok
Updated the settings page to require the old password
365
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
366
    # If the user is trying to set a new password, check that they have
367
    # entered old password and it authenticates.
368
    if fields.getfirst('password') is not None:
1078 by chadnickbok
Updated the settings page to require the old password
369
        try:
1094 by me at id
www/apps/userservice#get_user: Fix fallout from the Storm migration.
370
            authenticate.authenticate(req.store, login, oldpassword)
1078 by chadnickbok
Updated the settings page to require the old password
371
        except AuthError:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
372
            # XXX: Duplicated!
1078 by chadnickbok
Updated the settings page to require the old password
373
            req.headers_out['X-IVLE-Action-Error'] = \
374
                urllib.quote("Old password incorrect.")
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
375
            raise BadRequest("Old password incorrect.")
1078 by chadnickbok
Updated the settings page to require the old password
376
1094 by me at id
www/apps/userservice#get_user: Fix fallout from the Storm migration.
377
    # Make a dict of fields to update
378
    for f in fieldlist:
379
        val = fields.getfirst(f)
380
        if val is not None:
381
            # Note: May be rolled back if auth check below fails
382
            setattr(user, f, val.value.decode('utf-8'))
383
        else:
384
            pass
385
486 by mattgiuca
caps: Added a few new capabilities.
386
    req.content_type = "text/plain"
940 by wagrant
userservice: Repeat after me: I will not talk to code that runs as root
387
    req.write('')
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
388
552 by mattgiuca
userservice: Small fixes to update_user.
389
def handle_get_user(req, fields):
390
    """
391
    Retrieve a user's account details. This returns all details which the db
554 by mattgiuca
userservice: Does not return svn_pass to the user.
392
    module is willing to give up, EXCEPT the following fields:
393
        svn_pass
552 by mattgiuca
userservice: Small fixes to update_user.
394
    """
395
    # Only give full powers if this user has CAP_GETUSER
396
    fullpowers = req.user.hasCap(caps.CAP_GETUSER)
397
398
    try:
399
        login = fields.getfirst('login')
400
        if login is None:
401
            raise AttributeError()
402
        if not fullpowers and login != req.user.login:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
403
            raise Unauthorized()
552 by mattgiuca
userservice: Small fixes to update_user.
404
    except AttributeError:
405
        # If login not specified, update yourself
406
        login = req.user.login
407
408
    # Just talk direct to the DB
1092 by me at id
www/apps/userservice#get_user: Set local_password in the output dict to True
409
    userobj = ivle.database.User.get_by_login(req.store, login)
410
    user = ivle.util.object_to_dict(user_fields_list, userobj)
674 by mattgiuca
userservice: Fixed encoding of User objects into JSON
411
    # Convert time stamps to nice strings
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
412
    for k in 'pass_exp', 'acct_exp', 'last_login':
413
        if user[k] is not None:
414
            user[k] = unicode(user[k])
415
1092 by me at id
www/apps/userservice#get_user: Set local_password in the output dict to True
416
    user['local_password'] = userobj.passhash is not None
417
554 by mattgiuca
userservice: Does not return svn_pass to the user.
418
    response = cjson.encode(user)
552 by mattgiuca
userservice: Small fixes to update_user.
419
    req.content_type = "text/plain"
420
    req.write(response)
421
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
422
def handle_get_enrolments(req, fields):
423
    """
995 by wagrant
common.db: Add get_enrolment_groups. Will return group information for
424
    Retrieve a user's enrolment details. Each enrolment includes any group
425
    memberships for that offering.
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
426
    """
427
    # For the moment we're only able to query ourselves
428
    fullpowers = False
429
    ## Only give full powers if this user has CAP_GETUSER
430
    ##fullpowers = req.user.hasCap(caps.CAP_GETUSER)
431
432
    try:
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
433
        user = ivle.database.User.get_by_login(req.store,
434
                    fields.getfirst('login'))
435
        if user is None:
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
436
            raise AttributeError()
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
437
        if not fullpowers and user != req.user:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
438
            raise Unauthorized()
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
439
    except AttributeError:
440
        # If login not specified, update yourself
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
441
        user = req.user
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
442
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
443
    dict_enrolments = []
444
    for e in user.active_enrolments:
445
        dict_enrolments.append({
446
            'offeringid':      e.offering.id,
447
            'subj_code':       e.offering.subject.code,
448
            'subj_name':       e.offering.subject.name,
449
            'subj_short_name': e.offering.subject.short_name,
450
            'url':             e.offering.subject.url,
451
            'year':            e.offering.semester.year,
452
            'semester':        e.offering.semester.semester,
1080.1.81 by William Grant
ivle.database.Enrolment: Add a groups attribute, containing groups of which
453
            'groups':          [{'name': group.name,
454
                                 'nick': group.nick} for group in e.groups]
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
455
        })
456
    response = cjson.encode(dict_enrolments)
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
457
    req.content_type = "text/plain"
458
    req.write(response)
459
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
460
def handle_get_active_offerings(req, fields):
461
    """Required cap: None
462
    Returns all the active offerings for a particular subject
463
    Required:
464
        subjectid
465
    """
466
467
    subjectid = fields.getfirst('subjectid')
468
    if subjectid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
469
        raise BadRequest("Required: subjectid")
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
470
    try:
471
        subjectid = int(subjectid)
472
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
473
        raise BadRequest("subjectid must be an integer")
1080.1.82 by William Grant
www/apps/userservice: Use Storm rather than get_offering_semesters.
474
475
    subject = req.store.get(ivle.database.Subject, subjectid)
476
477
    response = cjson.encode([{'offeringid': offering.id,
478
                              'subj_name': offering.subject.name,
479
                              'year': offering.semester.year,
480
                              'semester': offering.semester.semester,
481
                              'active': offering.semester.active
482
                             } for offering in subject.offerings
483
                                    if offering.semester.active])
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
484
    req.content_type = "text/plain"
485
    req.write(response)
486
487
def handle_get_project_groups(req, fields):
488
    """Required cap: None
489
    Returns all the project groups in an offering grouped by project set
490
    Required:
491
        offeringid
492
    """
493
494
    offeringid = fields.getfirst('offeringid')
495
    if offeringid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
496
        raise BadRequest("Required: offeringid")
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
497
    try:
498
        offeringid = int(offeringid)
499
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
500
        raise BadRequest("offeringid must be an integer")
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
501
502
    offering = req.store.get(ivle.database.Offering, offeringid)
503
504
    dict_projectsets = []
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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
        })
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
513
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
514
    response = cjson.encode(dict_projectsets)
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
515
    req.write(response)
516
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
517
@require_method('POST')
518
@require_cap(caps.CAP_MANAGEGROUPS)
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
519
def handle_create_group(req, fields):
520
    """Required cap: CAP_MANAGEGROUPS
521
    Creates a project group in a specific project set
522
    Required:
523
        projectsetid, groupnm
524
    Optional:
525
        nick
526
    Returns:
527
        groupid
528
    """
529
    # Get required fields
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
530
    projectsetid = fields.getfirst('projectsetid').value
531
    groupnm = fields.getfirst('groupnm').value
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
532
    if projectsetid is None or groupnm is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
533
        raise BadRequest("Required: projectsetid, groupnm")
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
534
    groupnm = unicode(groupnm)
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
535
    try:
536
        projectsetid = int(projectsetid)
537
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
538
        raise BadRequest("projectsetid must be an integer")
539
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
540
    # Get optional fields
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
541
    nick = fields.getfirst('nick').value
542
    if nick is not None:
543
        nick = unicode(nick)
1006 by dcoles
Groups: Use db transactions for risky usermanagement operations. (Such as when
544
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
568
    try:
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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))
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
576
577
    req.content_type = "text/plain"
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
578
    req.write('')
981 by dcoles
Groups: Added in support for creating groups in the database through
579
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
580
def handle_get_group_membership(req, fields):
581
    """ Required cap: None
582
    Returns two lists. One of people in the group and one of people not in the 
583
    group (but enroled in the offering)
584
    Required:
585
        groupid, offeringid
586
    """
587
    # Get required fields
588
    groupid = fields.getfirst('groupid')
589
    offeringid = fields.getfirst('offeringid')
590
    if groupid is None or offeringid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
591
        raise BadRequest("Required: groupid, offeringid")
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
592
    try:
593
        groupid = int(groupid)
594
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
595
        raise BadRequest("groupid must be an integer")
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
596
    group = req.store.get(ivle.database.ProjectGroup, groupid)
597
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
598
    try:
599
        offeringid = int(offeringid)
600
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
601
        raise BadRequest("offeringid must be an integer")
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
602
    offering = req.store.get(ivle.database.Offering, offeringid)
603
604
605
    offeringmembers = [{'login': user.login,
606
                        'fullname': user.fullname
607
                       } for user in offering.members.order_by(
608
                            ivle.database.User.login)
609
                      ]
610
    groupmembers = [{'login': user.login,
611
                        'fullname': user.fullname
612
                       } for user in group.members.order_by(
613
                            ivle.database.User.login)
614
                      ]
615
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
616
    # Make sure we don't include members in both lists
617
    for member in groupmembers:
618
        if member in offeringmembers:
619
            offeringmembers.remove(member)
620
621
    response = cjson.encode(
622
        {'groupmembers': groupmembers, 'available': offeringmembers})
623
624
    req.content_type = "text/plain"
625
    req.write(response)
626
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
627
@require_method('POST')
628
@require_cap(caps.CAP_MANAGEGROUPS)
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
629
def handle_assign_group(req, fields):
630
    """ Required cap: CAP_MANAGEGROUPS
631
    Assigns a user to a project group
632
    Required:
633
        login, groupid
634
    """
635
    # Get required fields
636
    login = fields.getfirst('login')
637
    groupid = fields.getfirst('groupid')
638
    if login is None or groupid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
639
        raise BadRequest("Required: login, groupid")
1080.1.84 by William Grant
wwww/apps/userservice: Create group memberships using Storm, not ivle.db.
640
641
    group = req.store.get(ivle.database.ProjectGroup, int(groupid))
642
    user = ivle.database.User.get_by_login(req.store, login)
643
644
    # Add membership to database
645
    # We can't keep a transaction open until the end here, as usrmgt-server
646
    # needs to see the changes!
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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': {}}
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
653
    try:
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
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))
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
661
662
    return(cjson.encode({'response': 'okay'}))
981 by dcoles
Groups: Added in support for creating groups in the database through
663
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
664
# Map action names (from the path)
665
# to actual function objects
666
actions_map = {
667
    "activate_me": handle_activate_me,
668
    "create_user": handle_create_user,
669
    "update_user": handle_update_user,
552 by mattgiuca
userservice: Small fixes to update_user.
670
    "get_user": handle_get_user,
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
671
    "get_enrolments": handle_get_enrolments,
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
672
    "get_active_offerings": handle_get_active_offerings,
673
    "get_project_groups": handle_get_project_groups,
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
674
    "get_group_membership": handle_get_group_membership,
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
675
    "create_group": handle_create_group,
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
676
    "assign_group": handle_assign_group,
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
677
}