~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
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
486 by mattgiuca
caps: Added a few new capabilities.
31
### Actions ###
32
33
# The one-and-only path segment to userservice determines the action being
34
# undertaken.
35
# All actions require that you are logged in.
36
# All actions require method = POST, unless otherwise stated.
37
38
# userservice/activate_me
39
# Required cap: None
40
# declaration = "I accept the IVLE Terms of Service"
41
# Activate the currently-logged-in user's account. Requires that "declaration"
42
# is as above, and that the user's state is "no_agreement".
43
44
# userservice/create_user
45
# Required cap: CAP_CREATEUSER
46
# Arguments are the same as the database columns for the "login" table.
47
# Required:
48
#   login, fullname, rolenm
49
# Optional:
50
#   password, nick, email, studentid
51
52
# TODO
53
# userservice/get_user
54
# method: May be GET
55
# Required cap: None to see yourself.
56
#   CAP_GETUSER to see another user.
57
# Gets the login details of a user. Returns as a JSON object.
58
# login = Optional login name of user to get. If omitted, get yourself.
59
60
# userservice/update_user
61
# Required cap: None to update yourself.
62
#   CAP_UPDATEUSER to update another user (and also more fields).
63
#   (This is all-powerful so should be only for admins)
64
# login = Optional login name of user to update. If omitted, update yourself.
65
# Other fields are optional, and will set the given field of the user.
66
# Without CAP_UPDATEUSER, you may change the following fields of yourself:
67
#   password, nick, email
68
# With CAP_UPDATEUSER, you may also change the following fields of any user:
69
#   password, nick, email, login, rolenm, unixid, fullname, studentid
70
# (You can't change "state", but see userservice/[en|dis]able_user).
71
72
# TODO
73
# userservice/enable_user
74
# Required cap: CAP_UPDATEUSER
75
# Enable a user whose account has been disabled. Does not work for
76
# no_agreement or pending users.
77
# login = Login name of user to enable.
78
79
# TODO
80
# userservice/disable_user
81
# Required cap: CAP_UPDATEUSER
82
# Disable a user's account. Does not work for no_agreement or pending users.
83
# login = Login name of user to disable.
84
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
85
import os
86
import sys
87
88
import cjson
89
478 by mattgiuca
scripts/usrmgr-server: Renamed actions from dashes to underscores.
90
import common
91
from common import (util, chat, caps)
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
92
import conf
93
487 by mattgiuca
setup.py: Added code to convert usrmgt_port into an int before writing to conf
94
from conf import (usrmgt_host, usrmgt_port, usrmgt_magic)
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
95
96
# The user must send this declaration message to ensure they acknowledge the
97
# TOS
98
USER_DECLARATION = "I accept the IVLE Terms of Service"
99
100
def handle(req):
101
    """Handler for the Console Service AJAX backend application."""
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
102
    if req.user is None:
478 by mattgiuca
scripts/usrmgr-server: Renamed actions from dashes to underscores.
103
        # Not logged in
104
        req.throw_error(req.HTTP_FORBIDDEN)
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
105
    if len(req.path) > 0 and req.path[-1] == os.sep:
106
        path = req.path[:-1]
107
    else:
108
        path = req.path
109
    # The path determines which "command" we are receiving
486 by mattgiuca
caps: Added a few new capabilities.
110
    fields = req.get_fieldstorage()
478 by mattgiuca
scripts/usrmgr-server: Renamed actions from dashes to underscores.
111
    if req.path == "activate_me":
486 by mattgiuca
caps: Added a few new capabilities.
112
        handle_activate_me(req, fields)
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
113
    else:
114
        req.throw_error(req.HTTP_BAD_REQUEST)
115
486 by mattgiuca
caps: Added a few new capabilities.
116
def handle_activate_me(req, fields):
465 by mattgiuca
Added new app: userservice, which is an ajax service for user management
117
    """Create the jail, svn, etc, for the currently logged in user (this is
118
    put in the queue for usermgt to do).
119
    This will block until usermgt returns, which could take seconds to minutes
120
    in the extreme. Therefore, it is designed to be called by Ajax, with a
121
    nice "Please wait" message on the frontend.
122
123
    This will signal that the user has accepted the terms of the license
124
    agreement, and will result in the user's database status being set to
125
    "enabled". (Note that it will be set to "pending" for the duration of the
126
    handling).
127
128
    As such, it takes a single POST field, "declaration", which
129
    must have the value, "I accept the IVLE Terms of Service".
130
    (Otherwise users could navigate to /userservice/createme without
131
    "accepting" the terms - at least this way requires them to acknowledge
132
    their acceptance). It must only be called through a POST request.
133
    """
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
134
    db = common.db.DB()
486 by mattgiuca
caps: Added a few new capabilities.
135
    try:
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
136
        if req.method != "POST":
137
            req.throw_error(req.HTTP_BAD_REQUEST)
138
        try:
139
            declaration = fields.getfirst('declaration')
140
        except AttributeError:
141
            req.throw_error(req.HTTP_BAD_REQUEST)
142
        if declaration != USER_DECLARATION:
143
            req.throw_error(req.HTTP_BAD_REQUEST)
144
145
        # Make sure the user's status is "no_agreement", and set status to
146
        # pending, within the one transaction. This ensures we only do this
147
        # one time.
148
        db.start_transaction()
149
        try:
150
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
151
            user_details = db.get_user(req.user.login)
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
152
            # Check that the user's status is "no_agreement".
153
            # (Both to avoid redundant calls, and to stop disabled users from
154
            # re-enabling their accounts).
500 by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries.
155
            if user_details.state != "no_agreement":
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
156
                req.throw_error(req.HTTP_BAD_REQUEST)
157
            # Write state "pending" to ensure we don't try this again
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
158
            db.update_user(req.user.login, state="pending")
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
159
        except:
160
            db.rollback()
161
            raise
162
        db.commit()
163
164
        # Get the arguments for usermgt.activate_user from the session
165
        # (The user must have already logged in to use this app)
166
        args = {
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
167
            "login": req.user.login,
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
168
        }
169
        msg = {'activate_user': args}
170
171
        response = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic,
172
            decode = False)
173
        # Write to the user's session to allow them to be activated
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
174
        req.user.state = "enabled"
498 by mattgiuca
apps/userservice: Now writes to req.session to avoid having to log out.
175
        session = req.get_session()
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
176
        session['user'] = req.user
498 by mattgiuca
apps/userservice: Now writes to req.session to avoid having to log out.
177
        session.save()
497 by mattgiuca
userservice: activate_me is now database enabled. It checks the state from the
178
        # Write the response
179
        req.content_type = "text/plain"
180
        req.write(response)
181
    finally:
182
        db.close()
486 by mattgiuca
caps: Added a few new capabilities.
183
184
create_user_fields_required = [
185
    'login', 'fullname', 'rolenm'
186
]
187
create_user_fields_optional = [
188
    'password', 'nick', 'email', 'studentid'
189
]
190
def handle_create_user(req, fields):
191
    """Create a new user, whose state is no_agreement.
192
    This does not create the user's jail, just an entry in the database which
193
    allows the user to accept an agreement.
194
    """
195
    if req.method != "POST":
196
        req.throw_error(req.HTTP_BAD_REQUEST)
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
197
    # Check if this user has CAP_UPDATEUSER
198
    if not req.user.hasCap(caps.CAP_UPDATEUSER):
486 by mattgiuca
caps: Added a few new capabilities.
199
        req.throw_error(req.HTTP_FORBIDDEN)
200
201
    # Make a dict of fields to create
202
    create = {}
203
    for f in create_user_fields_required:
204
        try:
205
            create[f] = fields.getfirst(f)
206
        except AttributeError:
207
            req.throw_error(req.HTTP_BAD_REQUEST)
208
    for f in create_user_fields_optional:
209
        try:
210
            create[f] = fields.getfirst(f)
211
        except AttributeError:
212
            pass
213
214
    # Get the arguments for usermgt.create_user from the session
215
    # (The user must have already logged in to use this app)
216
    msg = {'create_user': create}
217
    # TEMP
218
    req.write(msg)
219
    return
220
    # END TEMP
221
487 by mattgiuca
setup.py: Added code to convert usrmgt_port into an int before writing to conf
222
    response = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic,
486 by mattgiuca
caps: Added a few new capabilities.
223
        decode = False)
224
    req.content_type = "text/plain"
225
    req.write(response)
226
227
update_user_fields_anyone = [
228
    'password', 'nick', 'email'
229
]
230
update_user_fields_admin = [
231
    'password', 'nick', 'email', 'login', 'rolenm', 'unixid', 'fullname',
232
    'studentid'
233
]
234
def handle_update_user(req, fields):
235
    """Update a user's account details.
236
    This can be done in a limited form by any user, on their own account,
237
    or with full powers by a user with CAP_UPDATEUSER on any account.
238
    """
239
    if req.method != "POST":
240
        req.throw_error(req.HTTP_BAD_REQUEST)
241
242
    # Only give full powers if this user has CAP_UPDATEUSER
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
243
    fullpowers = req.user.hasCap(caps.CAP_UPDATEUSER)
486 by mattgiuca
caps: Added a few new capabilities.
244
    # List of fields that may be changed
245
    fieldlist = (update_user_fields_admin if fullpowers
246
        else update_user_fields_anyone)
247
248
    try:
249
        login = fields.getfirst('login')
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
250
        if not fullpowers and login != req.user.login:
486 by mattgiuca
caps: Added a few new capabilities.
251
            # Not allowed to edit other users
252
            req.throw_error(req.HTTP_FORBIDDEN)
253
    except AttributeError:
254
        # If login not specified, update yourself
506 by mattgiuca
dispatch.__init__, dispatch.request, cgirequest:
255
        login = req.user.login
486 by mattgiuca
caps: Added a few new capabilities.
256
257
    # Make a dict of fields to update
258
    update = {}
259
    for f in fieldlist:
260
        try:
261
            update[f] = fields.getfirst(f)
262
        except AttributeError:
263
            pass
264
265
    # Get the arguments for usermgt.create_user from the session
266
    # (The user must have already logged in to use this app)
267
    args = {
505 by mattgiuca
dispatch.html, consoleservice, userservice, interpret:
268
        "login": req.user.login,
486 by mattgiuca
caps: Added a few new capabilities.
269
        "update": update,
270
    }
271
    msg = {'update_user': args}
272
    # TEMP
273
    req.write(msg)
274
    return
275
    # END TEMP
276
487 by mattgiuca
setup.py: Added code to convert usrmgt_port into an int before writing to conf
277
    response = chat.chat(usrmgt_host, usrmgt_port, msg, usrmgt_magic,
486 by mattgiuca
caps: Added a few new capabilities.
278
        decode = False)
279
    req.content_type = "text/plain"
280
    req.write(response)