~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
# TODO
47
# userservice/enable_user
48
# Required cap: CAP_UPDATEUSER
49
# Enable a user whose account has been disabled. Does not work for
50
# no_agreement or pending users.
51
# login = Login name of user to enable.
52
53
# TODO
54
# userservice/disable_user
55
# Required cap: CAP_UPDATEUSER
56
# Disable a user's account. Does not work for no_agreement or pending users.
57
# login = Login name of user to disable.
58
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
59
# userservice/get_enrolments
60
# Required cap: None (for yourself)
61
# Returns a JSON encoded listing of a students is enrollments
62
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
63
# userservice/get_active_offerings(req, fields):
64
# Required cap: None
65
# Returns all the active offerings for a particular subject
66
# Required:
67
#   subjectid
68
981 by dcoles
Groups: Added in support for creating groups in the database through
69
# PROJECTS AND GROUPS
70
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
71
# userservice/get_project_groups
72
# Required cap: None
73
# Returns all the project groups in an offering grouped by project set
74
# Required:
75
#   offeringid
76
981 by dcoles
Groups: Added in support for creating groups in the database through
77
# userservice/create_project_set
78
# Required cap: CAP_MANAGEPROJECTS
984 by dcoles
Groups: Added userservice/create_project call. You can now create a project in
79
# Creates a project set for a offering
981 by dcoles
Groups: Added in support for creating groups in the database through
80
# Required:
81
#   offeringid, max_students_per_group
984 by dcoles
Groups: Added userservice/create_project call. You can now create a project in
82
# Returns:
83
#   projectsetid
981 by dcoles
Groups: Added in support for creating groups in the database through
84
85
# userservice/create_project
86
# Required cap: CAP_MANAGEPROJECTS
87
# Creates a project in a specific project set
88
# Required:
89
#   projectsetid
90
# Optional:
984 by dcoles
Groups: Added userservice/create_project call. You can now create a project in
91
#   synopsis, url, deadline
92
# Returns:
93
#   projectid
981 by dcoles
Groups: Added in support for creating groups in the database through
94
95
# userservice/create_group
96
# Required cap: CAP_MANAGEGROUPS
97
# Creates a project group in a specific project set
98
# Required:
99
#   projectsetid, groupnm
100
# Optional:
101
#   nick
102
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
103
# userservice/get_group_membership
104
# Required cap: None
105
# Returns two lists. One of people in the group and one of people not in the 
106
# group (but enroled in the offering)
107
# Required:
108
#   groupid
109
981 by dcoles
Groups: Added in support for creating groups in the database through
110
# userservice/assign_to_group
111
# Required cap: CAP_MANAGEGROUPS
112
# 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
113
# Required: login, groupid
981 by dcoles
Groups: Added in support for creating groups in the database through
114
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
115
import os
116
import sys
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
117
import datetime
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
118
119
import cjson
120
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
121
import ivle.database
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
122
from ivle import (util, chat)
1099.1.161 by William Grant
Move ivle.dispatch.login.get_user_details() to ivle.webapp.security.
123
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
124
import ivle.pulldown_subj
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
125
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
126
from ivle.rpc.decorators import require_method, require_role_anywhere, \
127
                                require_admin
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
128
1080.1.12 by me at id
ivle.auth.autherror: Remove, moving AuthError into ivle.auth itself.
129
from ivle.auth import AuthError, authenticate
1078 by chadnickbok
Updated the settings page to require the old password
130
import urllib
131
1099.1.132 by William Grant
Direct port of userservice to the new framework.
132
from ivle.webapp.base.views import BaseView
133
from ivle.webapp.base.plugins import ViewPlugin
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
134
from ivle.webapp.errors import NotFound, BadRequest, Unauthorized
1294.2.65 by William Grant
Port userservice.
135
from ivle.webapp import ApplicationRoot
1099.1.132 by William Grant
Direct port of userservice to the new framework.
136
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
137
# The user must send this declaration message to ensure they acknowledge the
138
# TOS
139
USER_DECLARATION = "I accept the IVLE Terms of Service"
140
1099.1.132 by William Grant
Direct port of userservice to the new framework.
141
class UserServiceView(BaseView):
1294.2.65 by William Grant
Port userservice.
142
    subpath_allowed = True
1099.1.132 by William Grant
Direct port of userservice to the new framework.
143
144
    def authorize(self, req):
1099.1.127 by William Grant
Implement ToS acceptance in the new login machinery. Now implemented through
145
        # 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.
146
        if req.path == 'activate_me' and get_user_details(req) is not None:
147
            return True
148
        return req.user is not None
149
150
    def render(self, req):
151
        # The path determines which "command" we are receiving
152
        fields = req.get_fieldstorage()
153
        try:
154
            func = actions_map[self.path]
155
        except KeyError:
156
            raise NotFound()
157
        func(req, fields)
158
1294.2.65 by William Grant
Port userservice.
159
    @property
160
    def path(self):
161
        return os.path.join(*self.subpath) if self.subpath else ''
162
163
1099.1.132 by William Grant
Direct port of userservice to the new framework.
164
class Plugin(ViewPlugin):
1294.2.65 by William Grant
Port userservice.
165
    views = [(ApplicationRoot, 'userservice', UserServiceView)]
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
166
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
167
@require_method('POST')
486 by mattgiuca
caps: Added a few new capabilities.
168
def handle_activate_me(req, fields):
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
169
    """Create the jail, svn, etc, for the currently logged in user (this is
170
    put in the queue for usermgt to do).
171
    This will block until usermgt returns, which could take seconds to minutes
172
    in the extreme. Therefore, it is designed to be called by Ajax, with a
173
    nice "Please wait" message on the frontend.
174
175
    This will signal that the user has accepted the terms of the license
176
    agreement, and will result in the user's database status being set to
177
    "enabled". (Note that it will be set to "pending" for the duration of the
178
    handling).
179
180
    As such, it takes a single POST field, "declaration", which
181
    must have the value, "I accept the IVLE Terms of Service".
182
    (Otherwise users could navigate to /userservice/createme without
183
    "accepting" the terms - at least this way requires them to acknowledge
184
    their acceptance). It must only be called through a POST request.
185
    """
1099.1.127 by William Grant
Implement ToS acceptance in the new login machinery. Now implemented through
186
1099.1.137 by William Grant
Actually set user in userservice/activate_me.
187
    user = get_user_details(req)
188
486 by mattgiuca
caps: Added a few new capabilities.
189
    try:
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
190
        declaration = fields.getfirst('declaration')
191
    except AttributeError:
192
        declaration = None      # Will fail next test
193
    if declaration != USER_DECLARATION:
194
        raise BadRequest()
195
196
    # Make sure the user's status is "no_agreement", and set status to
197
    # pending, within the one transaction. This ensures we only do this
198
    # one time.
199
    try:
200
        # Check that the user's status is "no_agreement".
201
        # (Both to avoid redundant calls, and to stop disabled users from
202
        # re-enabling their accounts).
203
        if user.state != "no_agreement":
204
            raise BadRequest("You have already agreed to the terms.")
205
        # Write state "pending" to ensure we don't try this again
206
        user.state = u"pending"
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
207
    except:
208
        req.store.rollback()
209
        raise
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
210
    req.store.commit()
211
212
    # Get the arguments for usermgt.activate_user from the session
213
    # (The user must have already logged in to use this app)
214
    args = {
215
        "login": user.login,
216
    }
217
    msg = {'activate_user': args}
218
219
    # Release our lock on the db so usrmgt can write
220
    req.store.rollback()
221
222
    # Try and contact the usrmgt server
223
    try:
1204 by William Grant
Use the config from the request, rather than ivle.conf, in userservice.
224
        response = chat.chat(req.config['usrmgt']['host'],
225
                             req.config['usrmgt']['port'],
226
                             msg,
227
                             req.config['usrmgt']['magic'],
228
                            )
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
229
    except cjson.DecodeError:
230
        # Gave back rubbish - set the response to failure
231
        response = {'response': 'usrmgt-failure'}
232
233
    # Get the staus of the users request
234
    try:
235
        status = response['response']
236
    except KeyError:
237
        status = 'failure'
238
239
    if status == 'okay':
240
        user.state = u"enabled"
241
    else:
242
        # Reset the user back to no agreement
243
        user.state = u"no_agreement"
244
245
    # Write the response
246
    req.content_type = "text/plain"
247
    req.write(cjson.encode(response))
486 by mattgiuca
caps: Added a few new capabilities.
248
249
create_user_fields_required = [
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
250
    'login', 'fullname',
486 by mattgiuca
caps: Added a few new capabilities.
251
]
252
create_user_fields_optional = [
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
253
    'admin', 'password', 'nick', 'email', 'studentid'
486 by mattgiuca
caps: Added a few new capabilities.
254
]
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
255
256
@require_method('POST')
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
257
@require_admin
486 by mattgiuca
caps: Added a few new capabilities.
258
def handle_create_user(req, fields):
259
    """Create a new user, whose state is no_agreement.
260
    This does not create the user's jail, just an entry in the database which
261
    allows the user to accept an agreement.
940 by wagrant
userservice: Repeat after me: I will not talk to code that runs as root
262
       Expected fields:
263
        login       - used as a unix login name and svn repository name.
264
                      STRING REQUIRED 
265
        password    - the clear-text password for the user. If this property is
266
                      absent or None, this is an indication that external
267
                      authentication should be used (i.e. LDAP).
268
                      STRING OPTIONAL
269
        email       - the user's email address.
270
                      STRING OPTIONAL
271
        nick        - the display name to use.
272
                      STRING REQUIRED
273
        fullname    - The name of the user for results and/or other official
274
                      purposes.
275
                      STRING REQUIRED
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
276
        admin       - Whether the user is an admin.
277
                      BOOLEAN REQUIRED
940 by wagrant
userservice: Repeat after me: I will not talk to code that runs as root
278
        studentid   - If supplied and not None, the student id of the user for
279
                      results and/or other official purposes.
280
                      STRING OPTIONAL
281
       Return Value: the uid associated with the user. INT
486 by mattgiuca
caps: Added a few new capabilities.
282
    """
283
    # Make a dict of fields to create
284
    create = {}
285
    for f in create_user_fields_required:
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
286
        val = fields.getfirst(f)
287
        if val is not None:
288
            create[f] = val
289
        else:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
290
            raise BadRequest("Required field %s missing." % repr(f))
486 by mattgiuca
caps: Added a few new capabilities.
291
    for f in create_user_fields_optional:
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
292
        val = fields.getfirst(f)
293
        if val is not None:
294
            create[f] = val
295
        else:
486 by mattgiuca
caps: Added a few new capabilities.
296
            pass
297
1080.1.73 by William Grant
www/apps/userservice: create_user now creates and enrols the User itself, not
298
    user = ivle.database.User(**create)
299
    req.store.add(user)
1279 by William Grant
Drop ivle.conf usage from ivle.pulldown_subj.
300
    ivle.pulldown_subj.enrol_user(req.config, req.store, user)
1080.1.21 by me at id
userservice/create_user: Use storm rather than ivle.db.get_user.
301
486 by mattgiuca
caps: Added a few new capabilities.
302
    req.content_type = "text/plain"
1080.1.73 by William Grant
www/apps/userservice: create_user now creates and enrols the User itself, not
303
    req.write(str(user.unixid))
486 by mattgiuca
caps: Added a few new capabilities.
304
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
305
def handle_get_enrolments(req, fields):
306
    """
995 by wagrant
common.db: Add get_enrolment_groups. Will return group information for
307
    Retrieve a user's enrolment details. Each enrolment includes any group
308
    memberships for that offering.
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
309
    """
310
    # For the moment we're only able to query ourselves
311
    fullpowers = False
312
313
    try:
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
314
        user = ivle.database.User.get_by_login(req.store,
315
                    fields.getfirst('login'))
316
        if user is None:
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
317
            raise AttributeError()
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
318
        if not fullpowers and user != req.user:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
319
            raise Unauthorized()
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
320
    except AttributeError:
321
        # 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.
322
        user = req.user
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
323
1080.1.28 by me at id
www/apps/groups: Use User.active_enrolments rather than ivle.db.get_enrolment.
324
    dict_enrolments = []
325
    for e in user.active_enrolments:
326
        dict_enrolments.append({
327
            'offeringid':      e.offering.id,
328
            'subj_code':       e.offering.subject.code,
329
            'subj_name':       e.offering.subject.name,
330
            'subj_short_name': e.offering.subject.short_name,
331
            'url':             e.offering.subject.url,
332
            'year':            e.offering.semester.year,
333
            'semester':        e.offering.semester.semester,
1080.1.81 by William Grant
ivle.database.Enrolment: Add a groups attribute, containing groups of which
334
            'groups':          [{'name': group.name,
335
                                 '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.
336
        })
337
    response = cjson.encode(dict_enrolments)
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
338
    req.content_type = "text/plain"
339
    req.write(response)
340
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
341
def handle_get_active_offerings(req, fields):
342
    """Required cap: None
343
    Returns all the active offerings for a particular subject
344
    Required:
345
        subjectid
346
    """
347
348
    subjectid = fields.getfirst('subjectid')
349
    if subjectid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
350
        raise BadRequest("Required: subjectid")
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
351
    try:
352
        subjectid = int(subjectid)
353
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
354
        raise BadRequest("subjectid must be an integer")
1080.1.82 by William Grant
www/apps/userservice: Use Storm rather than get_offering_semesters.
355
356
    subject = req.store.get(ivle.database.Subject, subjectid)
357
358
    response = cjson.encode([{'offeringid': offering.id,
359
                              'subj_name': offering.subject.name,
360
                              'year': offering.semester.year,
361
                              'semester': offering.semester.semester,
1104 by William Grant
Replace Semester.active with Semester.state, allowing more useful state
362
                              'active': True # XXX: Eliminate from protocol.
1080.1.82 by William Grant
www/apps/userservice: Use Storm rather than get_offering_semesters.
363
                             } for offering in subject.offerings
1104 by William Grant
Replace Semester.active with Semester.state, allowing more useful state
364
                                    if offering.semester.state == 'current'])
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
365
    req.content_type = "text/plain"
366
    req.write(response)
367
368
def handle_get_project_groups(req, fields):
369
    """Required cap: None
370
    Returns all the project groups in an offering grouped by project set
371
    Required:
372
        offeringid
373
    """
374
375
    offeringid = fields.getfirst('offeringid')
376
    if offeringid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
377
        raise BadRequest("Required: offeringid")
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
378
    try:
379
        offeringid = int(offeringid)
380
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
381
        raise BadRequest("offeringid must be an integer")
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
382
383
    offering = req.store.get(ivle.database.Offering, offeringid)
384
385
    dict_projectsets = []
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
386
    for p in offering.project_sets:
387
        dict_projectsets.append({
388
            'projectsetid': p.id,
389
            'max_students_per_group': p.max_students_per_group,
390
            'groups': [{'groupid': g.id,
391
                        'groupnm': g.name,
392
                        'nick': g.nick} for g in p.project_groups]
393
        })
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
394
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
395
    response = cjson.encode(dict_projectsets)
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
396
    req.write(response)
397
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
398
@require_method('POST')
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
399
@require_role_anywhere('tutor', 'lecturer')
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
400
def handle_create_group(req, fields):
401
    """Required cap: CAP_MANAGEGROUPS
402
    Creates a project group in a specific project set
403
    Required:
404
        projectsetid, groupnm
405
    Optional:
406
        nick
407
    Returns:
408
        groupid
409
    """
410
    # Get required fields
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
411
    projectsetid = fields.getfirst('projectsetid').value
412
    groupnm = fields.getfirst('groupnm').value
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
413
    if projectsetid is None or groupnm is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
414
        raise BadRequest("Required: projectsetid, groupnm")
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
415
    groupnm = unicode(groupnm)
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
416
    try:
417
        projectsetid = int(projectsetid)
418
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
419
        raise BadRequest("projectsetid must be an integer")
420
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
421
    # Get optional fields
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
422
    nick = fields.getfirst('nick').value
423
    if nick is not None:
424
        nick = unicode(nick)
1006 by dcoles
Groups: Use db transactions for risky usermanagement operations. (Such as when
425
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
426
    group = ivle.database.ProjectGroup(name=groupnm,
427
                                       project_set_id=projectsetid,
428
                                       nick=nick,
429
                                       created_by=req.user,
430
                                       epoch=datetime.datetime.now())
431
    req.store.add(group)
432
433
    # Create the group repository
434
    # Yes, this is ugly, and it would be nice to just pass in the groupid,
435
    # but the object isn't visible to the extra transaction in
436
    # usrmgt-server until we commit, which we only do once the repo is
437
    # created.
438
    offering = group.project_set.offering
439
440
    args = {
441
        "subj_short_name": offering.subject.short_name,
442
        "year": offering.semester.year,
443
        "semester": offering.semester.semester,
444
        "groupnm": group.name,
445
    }
446
    msg = {'create_group_repository': args}
447
448
    # Contact the usrmgt server
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
449
    try:
1204 by William Grant
Use the config from the request, rather than ivle.conf, in userservice.
450
        usrmgt = chat.chat(req.config['usrmgt']['host'],
451
                           req.config['usrmgt']['port'],
452
                           msg,
453
                           req.config['usrmgt']['magic'],
454
                          )
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
455
    except cjson.DecodeError, e:
456
        raise Exception("Could not understand usrmgt server response:" +
457
                        e.message)
458
459
    if 'response' not in usrmgt or usrmgt['response']=='failure':
460
        raise Exception("Failure creating repository: " + str(usrmgt))
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
461
462
    req.content_type = "text/plain"
1080.1.80 by William Grant
www/apps/userservice: Port create_group to Storm.
463
    req.write('')
981 by dcoles
Groups: Added in support for creating groups in the database through
464
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
465
def handle_get_group_membership(req, fields):
466
    """ Required cap: None
467
    Returns two lists. One of people in the group and one of people not in the 
468
    group (but enroled in the offering)
469
    Required:
470
        groupid, offeringid
471
    """
472
    # Get required fields
473
    groupid = fields.getfirst('groupid')
474
    offeringid = fields.getfirst('offeringid')
475
    if groupid is None or offeringid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
476
        raise BadRequest("Required: groupid, offeringid")
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
477
    try:
478
        groupid = int(groupid)
479
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
480
        raise BadRequest("groupid must be an integer")
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
481
    group = req.store.get(ivle.database.ProjectGroup, groupid)
482
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
483
    try:
484
        offeringid = int(offeringid)
485
    except:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
486
        raise BadRequest("offeringid must be an integer")
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
487
    offering = req.store.get(ivle.database.Offering, offeringid)
488
489
490
    offeringmembers = [{'login': user.login,
491
                        'fullname': user.fullname
492
                       } for user in offering.members.order_by(
493
                            ivle.database.User.login)
494
                      ]
495
    groupmembers = [{'login': user.login,
496
                        'fullname': user.fullname
497
                       } for user in group.members.order_by(
498
                            ivle.database.User.login)
499
                      ]
500
1004 by dcoles
Groups: Now you can add people to a group as well as creating groups from the
501
    # Make sure we don't include members in both lists
502
    for member in groupmembers:
503
        if member in offeringmembers:
504
            offeringmembers.remove(member)
505
506
    response = cjson.encode(
507
        {'groupmembers': groupmembers, 'available': offeringmembers})
508
509
    req.content_type = "text/plain"
510
    req.write(response)
511
1080.1.90 by William Grant
ivle.rpc.decorators: Add (new package, too). Has a couple of decorators to
512
@require_method('POST')
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
513
@require_role_anywhere('tutor', 'lecturer')
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
514
def handle_assign_group(req, fields):
515
    """ Required cap: CAP_MANAGEGROUPS
516
    Assigns a user to a project group
517
    Required:
518
        login, groupid
519
    """
520
    # Get required fields
521
    login = fields.getfirst('login')
522
    groupid = fields.getfirst('groupid')
523
    if login is None or groupid is None:
1099.1.134 by William Grant
Replace most userservice req.throw_error()s with new exceptions.
524
        raise BadRequest("Required: login, groupid")
1080.1.84 by William Grant
wwww/apps/userservice: Create group memberships using Storm, not ivle.db.
525
526
    group = req.store.get(ivle.database.ProjectGroup, int(groupid))
527
    user = ivle.database.User.get_by_login(req.store, login)
528
529
    # Add membership to database
530
    # We can't keep a transaction open until the end here, as usrmgt-server
531
    # needs to see the changes!
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
532
    group.members.add(user)
533
    req.store.commit()
534
535
    # Rebuild the svn config file
536
    # Contact the usrmgt server
537
    msg = {'rebuild_svn_group_config': {}}
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
538
    try:
1204 by William Grant
Use the config from the request, rather than ivle.conf, in userservice.
539
        usrmgt = chat.chat(req.config['usrmgt']['host'],
540
                           req.config['usrmgt']['port'],
541
                           msg,
542
                           req.config['usrmgt']['magic'],
543
                          )
1099.1.136 by William Grant
Remove some useless try-except blocks that just raise a 500.
544
    except cjson.DecodeError, e:
545
        raise Exception("Could not understand usrmgt server response: %s" +
546
                        e.message)
547
548
        if 'response' not in usrmgt or usrmgt['response']=='failure':
549
            raise Exception("Failure creating repository: " + str(usrmgt))
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
550
551
    return(cjson.encode({'response': 'okay'}))
981 by dcoles
Groups: Added in support for creating groups in the database through
552
1181 by William Grant
Allow revocation of group memberships by tutors and lecturers.
553
@require_method('POST')
554
@require_role_anywhere('tutor', 'lecturer')
555
def handle_unassign_group(req, fields):
556
    """Remove a user from a project group.
557
558
    The user is removed from the group in the database, and access to the
559
    group Subversion repository is revoked.
560
561
    Note that any checkouts will remain, although they will be unusable.
562
    """
563
564
    # Get required fields
565
    login = fields.getfirst('login')
566
    groupid = fields.getfirst('groupid')
567
    if login is None or groupid is None:
568
        raise BadRequest("Required: login, groupid")
569
570
    group = req.store.get(ivle.database.ProjectGroup, int(groupid))
571
    user = ivle.database.User.get_by_login(req.store, login)
572
573
    # Remove membership from the database
574
    # We can't keep a transaction open until the end here, as usrmgt-server
575
    # needs to see the changes!
576
    group.members.remove(user)
577
    req.store.commit()
578
579
    # Rebuild the svn config file
580
    # Contact the usrmgt server
581
    msg = {'rebuild_svn_group_config': {}}
582
    try:
1204 by William Grant
Use the config from the request, rather than ivle.conf, in userservice.
583
        usrmgt = chat.chat(req.config['usrmgt']['host'],
584
                           req.config['usrmgt']['port'],
585
                           msg,
586
                           req.config['usrmgt']['magic'],
587
                          )
1181 by William Grant
Allow revocation of group memberships by tutors and lecturers.
588
    except cjson.DecodeError, e:
589
        raise Exception("Could not understand usrmgt server response: %s" +
590
                        e.message)
591
592
        if 'response' not in usrmgt or usrmgt['response']=='failure':
593
            raise Exception("Failure creating repository: " + str(usrmgt))
594
595
    return(cjson.encode({'response': 'okay'}))
596
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
597
# Map action names (from the path)
598
# to actual function objects
599
actions_map = {
600
    "activate_me": handle_activate_me,
601
    "create_user": handle_create_user,
930 by dcoles
Userservice: Added get_enrolments handler to allow a JSON query of a students
602
    "get_enrolments": handle_get_enrolments,
1001 by dcoles
Groups: Added the view half of the group admin panel. This lets people with the
603
    "get_active_offerings": handle_get_active_offerings,
604
    "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
605
    "get_group_membership": handle_get_group_membership,
986 by dcoles
Groups: Added userservice/create_group call. You can now create a project in
606
    "create_group": handle_create_group,
989 by dcoles
Groups: Added userservice/assign_group call. This allows a user with the
607
    "assign_group": handle_assign_group,
1181 by William Grant
Allow revocation of group memberships by tutors and lecturers.
608
    "unassign_group": handle_unassign_group,
536 by mattgiuca
apps/userservice: Generalised searching for actions (dict mapping action names to
609
}