18
18
# Author: Matt Giuca, Will Grant
21
import formencode.validators
22
from genshi.filters import HTMLFormFiller
24
from ivle.webapp import ApplicationRoot
25
from ivle.webapp.base.xhtml import XHTMLView
26
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
27
from ivle.webapp.admin.publishing import root_to_user, user_url
28
from ivle.database import User
32
class UsersView(XHTMLView):
33
"""A list of all IVLE users."""
34
template = 'templates/users.html'
36
breadcrumb_text = 'Users'
38
def authorize(self, req):
39
return req.user and req.user.admin
41
def populate(self, req, ctx):
43
ctx['users'] = req.store.find(User).order_by(User.login)
46
class UserEditSchema(formencode.Schema):
47
nick = formencode.validators.UnicodeString(not_empty=True)
48
email = formencode.validators.Email(not_empty=False,
51
class UserEditView(XHTMLView):
52
"""A form to change a user's details."""
53
template = 'templates/user-edit.html'
57
def filter(self, stream, ctx):
58
return stream | HTMLFormFiller(data=ctx['data'])
60
def populate(self, req, ctx):
61
if req.method == 'POST':
62
data = dict(req.get_fieldstorage())
64
validator = UserEditSchema()
65
data = validator.to_python(data, state=req)
66
self.context.nick = data['nick']
67
self.context.email = unicode(data['email']) if data['email'] \
70
req.throw_redirect(req.uri)
71
except formencode.Invalid, e:
72
errors = e.unpack_errors()
74
data = {'nick': self.context.nick,
75
'email': self.context.email
79
ctx['format_datetime'] = ivle.date.make_date_nice
80
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
83
ctx['user'] = self.context
85
ctx['errors'] = errors
87
class UserAdminSchema(formencode.Schema):
88
admin = formencode.validators.StringBoolean(if_missing=False)
89
disabled = formencode.validators.StringBoolean(if_missing=False)
90
fullname = formencode.validators.UnicodeString(not_empty=True)
91
studentid = formencode.validators.UnicodeString(not_empty=False,
95
class UserAdminView(XHTMLView):
96
"""A form for admins to change more of a user's details."""
97
template = 'templates/user-admin.html'
100
def authorize(self, req):
101
"""Only allow access if the requesting user is an admin."""
102
return req.user and req.user.admin
104
def filter(self, stream, ctx):
105
return stream | HTMLFormFiller(data=ctx['data'])
107
def populate(self, req, ctx):
108
if req.method == 'POST':
109
data = dict(req.get_fieldstorage())
111
validator = UserAdminSchema()
112
data = validator.to_python(data, state=req)
114
if self.context is req.user:
115
# Admin checkbox is disabled -- assume unchanged
116
data['admin'] = self.context.admin
117
data['disabled'] = self.context.state == u'disabled'
119
self.context.admin = data['admin']
120
if self.context.state in (u'enabled', u'disabled'):
121
self.context.state = (u'disabled' if data['disabled']
123
self.context.fullname = data['fullname'] \
124
if data['fullname'] else None
125
self.context.studentid = data['studentid'] \
126
if data['studentid'] else None
128
req.throw_redirect(req.uri)
129
except formencode.Invalid, e:
130
errors = e.unpack_errors()
132
data = {'admin': self.context.admin,
133
'disabled': self.context.state == u'disabled',
134
'fullname': self.context.fullname,
135
'studentid': self.context.studentid,
140
ctx['user'] = self.context
141
# Disable the Admin checkbox if editing oneself
142
ctx['disable_admin'] = self.context is req.user
144
ctx['errors'] = errors
146
class PasswordChangeView(XHTMLView):
147
"""A form to change a user's password, with knowledge of the old one."""
148
template = 'templates/user-password-change.html'
152
def authorize(self, req):
153
"""Only allow access if the requesting user holds the permission,
154
and the target user has a password set. Otherwise we might be
155
clobbering external authn.
157
return super(PasswordChangeView, self).authorize(req) and \
158
self.context.passhash is not None
160
def populate(self, req, ctx):
162
if req.method == 'POST':
163
data = dict(req.get_fieldstorage())
164
if data.get('old_password') is None or \
165
not self.context.authenticate(data.get('old_password')):
166
error = 'Incorrect password.'
167
elif data.get('new_password') != data.get('new_password_again'):
168
error = 'New passwords do not match.'
169
elif not data.get('new_password'):
170
error = 'New password cannot be empty.'
172
self.context.password = data['new_password']
174
req.throw_redirect(req.uri)
177
ctx['user'] = self.context
180
class PasswordResetView(XHTMLView):
181
"""A form to reset a user's password, without knowledge of the old one."""
182
template = 'templates/user-password-reset.html'
185
def authorize(self, req):
186
"""Only allow access if the requesting user is an admin."""
187
return req.user and req.user.admin
189
def populate(self, req, ctx):
191
if req.method == 'POST':
192
data = dict(req.get_fieldstorage())
193
if data.get('new_password') != data.get('new_password_again'):
194
error = 'New passwords do not match.'
195
elif not data.get('new_password'):
196
error = 'New password cannot be empty.'
198
self.context.password = data['new_password']
200
req.throw_redirect(req.uri)
202
ctx['user'] = self.context
205
class Plugin(ViewPlugin, MediaPlugin):
20
from ivle.webapp.base.views import JSONRESTView
21
from ivle.webapp.base.plugins import BasePlugin
25
# List of fields returned as part of the user JSON dictionary
26
# (as returned by the get_user action)
28
"login", "state", "unixid", "email", "nick", "fullname",
29
"rolenm", "studentid", "acct_exp", "pass_exp", "last_login",
33
class UserRESTView(JSONRESTView):
35
A REST interface to the user object.
37
def __init__(self, req, login):
38
super(UserRESTView, self).__init__(self, req, login)
39
self.context = ivle.database.User.get_by_login(req.store, login)
43
user = ivle.util.object_to_dict(user_fields_list, self.context)
44
# Convert time stamps to nice strings
45
for k in 'pass_exp', 'acct_exp', 'last_login':
46
if user[k] is not None:
47
user[k] = unicode(user[k])
49
user['local_password'] = self.context.passhash is not None
52
def PATCH(self, req, data):
54
# XXX Admins can set extra fields
55
# Note: Cannot change password here (use change_password named op)
57
for f in user_fields_list:
60
if isinstance(field, str):
61
field = unicode(field)
62
setattr(self.context, f, field)
66
class Plugin(BasePlugin):
207
68
The Plugin class for the user plugin.
210
forward_routes = (root_to_user,)
211
reverse_routes = (user_url,)
212
views = [(ApplicationRoot, 'users', UsersView),
213
(User, '+index', UserEditView),
214
(User, '+admin', UserAdminView),
215
(User, '+changepassword', PasswordChangeView),
216
(User, '+resetpassword', PasswordResetView),
220
('users', 'Users', 'Display and edit all users',
221
'users.png', 'users', 90, True)
70
# Magic attribute: urls
71
# Sequence of pairs/triples of
72
# (regex str, handler class, kwargs dict)
73
# The kwargs dict is passed to the __init__ of the view object
75
('api/users/:login', UserRESTView)
224
public_forward_routes = forward_routes
225
public_reverse_routes = reverse_routes