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

1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 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
# Author: Matt Giuca, Will Grant
19
1294.1.1 by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView.
20
import formencode
21
import formencode.validators
22
from genshi.filters import HTMLFormFiller
23
1375 by William Grant
Add a user list -- as yet unlinked.
24
from ivle.webapp import ApplicationRoot
1099.1.112 by William Grant
Implement authorization in JSON REST views. Add security declarations to
25
from ivle.webapp.base.rest import JSONRESTView, require_permission
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
26
from ivle.webapp.base.xhtml import XHTMLView
1099.1.99 by William Grant
Require that plugins providing media subclass MediaPlugin.
27
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
1294.3.2 by William Grant
Router->Publisher
28
from ivle.webapp.admin.publishing import root_to_user, user_url
1375 by William Grant
Add a user list -- as yet unlinked.
29
from ivle.database import User
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
30
import ivle.database
1294.1.7 by William Grant
Display account/password expiry times on +edit.
31
import ivle.date
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
32
import ivle.util
33
1375 by William Grant
Add a user list -- as yet unlinked.
34
35
class UsersView(XHTMLView):
36
    """A list of all IVLE users."""
37
    template = 'templates/users.html'
1497 by Matt Giuca
Added Users tab to drop-down menu, for admins only.
38
    tab = 'users'
1472 by William Grant
Add a 'Users' breadcrumb.
39
    breadcrumb_text = 'Users'
1375 by William Grant
Add a user list -- as yet unlinked.
40
41
    def authorize(self, req):
1504 by Matt Giuca
ivle/webapp/admin/user.py: Fixed crash when visiting admin-only user pages while logged out (None dereference).
42
        return req.user and req.user.admin
1375 by William Grant
Add a user list -- as yet unlinked.
43
44
    def populate(self, req, ctx):
45
        ctx['req'] = req
46
        ctx['users'] = req.store.find(User).order_by(User.login)
47
48
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
49
# List of fields returned as part of the user JSON dictionary
50
# (as returned by the get_user action)
51
user_fields_list = (
52
    "login", "state", "unixid", "email", "nick", "fullname",
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
53
    "admin", "studentid", "acct_exp", "pass_exp", "last_login",
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
54
    "svn_pass"
55
)
56
57
class UserRESTView(JSONRESTView):
58
    """
59
    A REST interface to the user object.
60
    """
61
1099.1.112 by William Grant
Implement authorization in JSON REST views. Add security declarations to
62
    @require_permission('view')
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
63
    def GET(self, req):
64
        # XXX Check Caps
65
        user = ivle.util.object_to_dict(user_fields_list, self.context)
66
        # Convert time stamps to nice strings
67
        for k in 'pass_exp', 'acct_exp', 'last_login':
68
            if user[k] is not None:
69
                user[k] = unicode(user[k])
70
71
        user['local_password'] = self.context.passhash is not None
72
        return user
73
1294.1.1 by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView.
74
class UserEditSchema(formencode.Schema):
75
    nick = formencode.validators.UnicodeString(not_empty=True)
76
    email = formencode.validators.Email(not_empty=False,
77
                                        if_missing=None)
78
79
class UserEditView(XHTMLView):
80
    """A form to change a user's details."""
81
    template = 'templates/user-edit.html'
1498 by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon
82
    tab = 'users'
1294.1.1 by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView.
83
    permission = 'edit'
84
85
    def filter(self, stream, ctx):
86
        return stream | HTMLFormFiller(data=ctx['data'])
87
88
    def populate(self, req, ctx):
89
        if req.method == 'POST':
90
            data = dict(req.get_fieldstorage())
91
            try:
92
                validator = UserEditSchema()
93
                data = validator.to_python(data, state=req)
94
                self.context.nick = data['nick']
95
                self.context.email = unicode(data['email']) if data['email'] \
96
                                     else None
97
                req.store.commit()
98
                req.throw_redirect(req.uri)
99
            except formencode.Invalid, e:
100
                errors = e.unpack_errors()
101
        else:
102
            data = {'nick': self.context.nick,
103
                    'email': self.context.email
104
                   }
105
            errors = {}
106
1294.1.7 by William Grant
Display account/password expiry times on +edit.
107
        ctx['format_datetime'] = ivle.date.make_date_nice
108
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
109
1294.1.5 by William Grant
Add a view for admins to reset other users' passwords.
110
        ctx['req'] = req
1294.1.1 by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView.
111
        ctx['user'] = self.context
112
        ctx['data'] = data
113
        ctx['errors'] = errors
114
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
115
class UserAdminSchema(formencode.Schema):
116
    admin = formencode.validators.StringBoolean(if_missing=False)
1503 by David Coles
Admin: Allow enabling and disabling of users in admin UI
117
    disabled = formencode.validators.StringBoolean(if_missing=False)
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
118
    fullname = formencode.validators.UnicodeString(not_empty=True)
119
    studentid = formencode.validators.UnicodeString(not_empty=False,
120
                                                    if_missing=None
121
                                                    )
122
123
class UserAdminView(XHTMLView):
124
    """A form for admins to change more of a user's details."""
125
    template = 'templates/user-admin.html'
1498 by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon
126
    tab = 'users'
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
127
128
    def authorize(self, req):
129
        """Only allow access if the requesting user is an admin."""
1504 by Matt Giuca
ivle/webapp/admin/user.py: Fixed crash when visiting admin-only user pages while logged out (None dereference).
130
        return req.user and req.user.admin
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
131
132
    def filter(self, stream, ctx):
133
        return stream | HTMLFormFiller(data=ctx['data'])
134
135
    def populate(self, req, ctx):
136
        if req.method == 'POST':
137
            data = dict(req.get_fieldstorage())
138
            try:
139
                validator = UserAdminSchema()
140
                data = validator.to_python(data, state=req)
141
1501 by Matt Giuca
User administration page: Do not let an admin user change their own admin checkbox (disable and don't let it change). This would be bad.
142
                if self.context is req.user:
143
                    # Admin checkbox is disabled -- assume unchanged
144
                    data['admin'] = self.context.admin
1503 by David Coles
Admin: Allow enabling and disabling of users in admin UI
145
                    data['disabled'] = self.context.state == u'disabled'
1501 by Matt Giuca
User administration page: Do not let an admin user change their own admin checkbox (disable and don't let it change). This would be bad.
146
                else:
147
                    self.context.admin = data['admin']
1503 by David Coles
Admin: Allow enabling and disabling of users in admin UI
148
                    if self.context.state in (u'enabled', u'disabled'):
149
                        self.context.state = (u'disabled' if data['disabled']
150
                                else u'enabled')
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
151
                self.context.fullname = data['fullname'] \
152
                                        if data['fullname'] else None
153
                self.context.studentid = data['studentid'] \
154
                                         if data['studentid'] else None
155
                req.store.commit()
156
                req.throw_redirect(req.uri)
157
            except formencode.Invalid, e:
158
                errors = e.unpack_errors()
159
        else:
160
            data = {'admin': self.context.admin,
1503 by David Coles
Admin: Allow enabling and disabling of users in admin UI
161
                    'disabled': self.context.state == u'disabled',
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
162
                    'fullname': self.context.fullname,
163
                    'studentid': self.context.studentid,
164
                   }
165
            errors = {}
166
167
        ctx['req'] = req
168
        ctx['user'] = self.context
1501 by Matt Giuca
User administration page: Do not let an admin user change their own admin checkbox (disable and don't let it change). This would be bad.
169
        # Disable the Admin checkbox if editing oneself
170
        ctx['disable_admin'] = self.context is req.user
1294.1.9 by William Grant
Add a UserAdminView, to set fullname/studentid/admin.
171
        ctx['data'] = data
172
        ctx['errors'] = errors
173
1294.1.2 by William Grant
Add a non-AJAX password change view.
174
class PasswordChangeView(XHTMLView):
1294.1.5 by William Grant
Add a view for admins to reset other users' passwords.
175
    """A form to change a user's password, with knowledge of the old one."""
1294.1.2 by William Grant
Add a non-AJAX password change view.
176
    template = 'templates/user-password-change.html'
1498 by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon
177
    tab = 'users'
1294.1.2 by William Grant
Add a non-AJAX password change view.
178
    permission = 'edit'
179
1294.1.4 by William Grant
Forbid access to +changepassword if there is no passhash.
180
    def authorize(self, req):
181
        """Only allow access if the requesting user holds the permission,
182
           and the target user has a password set. Otherwise we might be
183
           clobbering external authn.
184
        """
185
        return super(PasswordChangeView, self).authorize(req) and \
186
               self.context.passhash is not None
187
1294.1.2 by William Grant
Add a non-AJAX password change view.
188
    def populate(self, req, ctx):
189
        error = None
190
        if req.method == 'POST':
191
            data = dict(req.get_fieldstorage())
192
            if data.get('old_password') is None or \
193
               not self.context.authenticate(data.get('old_password')):
194
                error = 'Incorrect password.'
195
            elif data.get('new_password') != data.get('new_password_again'):
196
                error = 'New passwords do not match.'
197
            elif not data.get('new_password'):
198
                error = 'New password cannot be empty.'
199
            else:
200
                self.context.password = data['new_password']
201
                req.store.commit()
202
                req.throw_redirect(req.uri)
203
1294.1.5 by William Grant
Add a view for admins to reset other users' passwords.
204
        ctx['req'] = req
205
        ctx['user'] = self.context
206
        ctx['error'] = error
207
208
class PasswordResetView(XHTMLView):
209
    """A form to reset a user's password, without knowledge of the old one."""
210
    template = 'templates/user-password-reset.html'
1498 by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon
211
    tab = 'users'
1294.1.5 by William Grant
Add a view for admins to reset other users' passwords.
212
213
    def authorize(self, req):
214
        """Only allow access if the requesting user is an admin."""
1504 by Matt Giuca
ivle/webapp/admin/user.py: Fixed crash when visiting admin-only user pages while logged out (None dereference).
215
        return req.user and req.user.admin
1294.1.5 by William Grant
Add a view for admins to reset other users' passwords.
216
217
    def populate(self, req, ctx):
218
        error = None
219
        if req.method == 'POST':
220
            data = dict(req.get_fieldstorage())
221
            if data.get('new_password') != data.get('new_password_again'):
222
                error = 'New passwords do not match.'
223
            elif not data.get('new_password'):
224
                error = 'New password cannot be empty.'
225
            else:
226
                self.context.password = data['new_password']
227
                req.store.commit()
228
                req.throw_redirect(req.uri)
229
1294.1.2 by William Grant
Add a non-AJAX password change view.
230
        ctx['user'] = self.context
231
        ctx['error'] = error
232
1099.1.99 by William Grant
Require that plugins providing media subclass MediaPlugin.
233
class Plugin(ViewPlugin, MediaPlugin):
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
234
    """
235
    The Plugin class for the user plugin.
236
    """
1294.2.19 by William Grant
Port ivle.webapp.admin.user's views to the new dispatch system.
237
1294.2.70 by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal.
238
    forward_routes = (root_to_user,)
239
    reverse_routes = (user_url,)
1375 by William Grant
Add a user list -- as yet unlinked.
240
    views = [(ApplicationRoot, 'users', UsersView),
241
             (ivle.database.User, '+index', UserEditView),
1294.2.140 by William Grant
Merge from trunk.
242
             (ivle.database.User, '+admin', UserAdminView),
243
             (ivle.database.User, '+changepassword', PasswordChangeView),
244
             (ivle.database.User, '+resetpassword', PasswordResetView),
1294.2.19 by William Grant
Port ivle.webapp.admin.user's views to the new dispatch system.
245
             (ivle.database.User, '+index', UserRESTView, 'api'),
246
             ]
1099.1.61 by William Grant
Port ivle.webapp.admin.user's media to the new framework.
247
1497 by Matt Giuca
Added Users tab to drop-down menu, for admins only.
248
    tabs = [
249
        ('users', 'Users', 'Display and edit all users',
1507 by William Grant
Shift the Users link to between Subjects and Help.
250
         'users.png', 'users', 90, True)
1497 by Matt Giuca
Added Users tab to drop-down menu, for admins only.
251
    ]
252
1294.2.138 by William Grant
Publish users in public mode.
253
    public_forward_routes = forward_routes
254
    public_reverse_routes = reverse_routes
255
1099.1.61 by William Grant
Port ivle.webapp.admin.user's media to the new framework.
256
    media = 'user-media'