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

« back to all changes in this revision

Viewing changes to ivle/webapp/admin/user.py

Add an XHTMLUnauthorizedView which redirects unauthenticated users to the
login page if a page raises an Unauthorized. Alter UserSettingsView to raise
one in the right cases, for testing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
# Author: Matt Giuca, Will Grant
19
19
 
20
 
import formencode
21
 
import formencode.validators
22
 
from genshi.filters import HTMLFormFiller
23
 
 
24
 
from ivle.database import User
25
 
import ivle.date
26
 
from ivle.pulldown_subj import enrol_user
27
 
from ivle.webapp import ApplicationRoot
28
 
from ivle.webapp.base.forms import BaseFormView, URLNameValidator
 
20
from ivle.webapp.base.rest import JSONRESTView
29
21
from ivle.webapp.base.xhtml import XHTMLView
30
 
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
31
 
from ivle.webapp.admin.publishing import root_to_user, user_url
32
 
 
33
 
 
34
 
class UsersView(XHTMLView):
35
 
    """A list of all IVLE users."""
36
 
    template = 'templates/users.html'
37
 
    tab = 'users'
38
 
    breadcrumb_text = 'Users'
39
 
 
40
 
    def authorize(self, req):
41
 
        return req.user and req.user.admin
42
 
 
43
 
    def populate(self, req, ctx):
44
 
        ctx['req'] = req
45
 
        ctx['users'] = req.store.find(User).order_by(User.login)
46
 
 
47
 
 
48
 
class UserEditSchema(formencode.Schema):
49
 
    nick = formencode.validators.UnicodeString(not_empty=True)
50
 
    email = formencode.validators.Email(not_empty=False,
51
 
                                        if_missing=None)
52
 
 
53
 
class UserEditView(BaseFormView):
54
 
    """A form to change a user's details."""
55
 
    template = 'templates/user-edit.html'
56
 
    tab = 'users'
57
 
    permission = 'edit'
58
 
 
59
 
    @property
60
 
    def validator(self):
61
 
        return UserEditSchema()
62
 
 
63
 
    def get_default_data(self, req):
64
 
        return {'nick': self.context.nick,
65
 
                'email': self.context.email
66
 
                }
67
 
 
68
 
    def save_object(self, req, data):
69
 
        self.context.nick = data['nick']
70
 
        self.context.email = unicode(data['email']) if data['email'] \
71
 
                             else None
72
 
        return self.context
73
 
 
74
 
    def populate(self, req, ctx):
75
 
        super(UserEditView, self).populate(req, ctx)
76
 
        ctx['format_datetime'] = ivle.date.make_date_nice
77
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
78
 
        ctx['svn_url'] = req.user.get_svn_url(req.config)
79
 
        ctx['svn_pass'] = req.user.svn_pass
80
 
 
81
 
 
82
 
class UserAdminSchema(formencode.Schema):
83
 
    admin = formencode.validators.StringBoolean(if_missing=False)
84
 
    disabled = formencode.validators.StringBoolean(if_missing=False)
85
 
    fullname = formencode.validators.UnicodeString(not_empty=True)
86
 
    studentid = formencode.validators.UnicodeString(not_empty=False,
87
 
                                                    if_missing=None
88
 
                                                    )
89
 
 
90
 
class UserAdminView(BaseFormView):
91
 
    """A form for admins to change more of a user's details."""
92
 
    template = 'templates/user-admin.html'
93
 
    tab = 'users'
94
 
 
95
 
    def authorize(self, req):
96
 
        """Only allow access if the requesting user is an admin."""
97
 
        return req.user and req.user.admin
98
 
 
99
 
    @property
100
 
    def validator(self):
101
 
        return UserAdminSchema()
102
 
 
103
 
    def get_default_data(self, req):
104
 
        return {'admin': self.context.admin,
105
 
                'disabled': self.context.state == u'disabled',
106
 
                'fullname': self.context.fullname,
107
 
                'studentid': self.context.studentid,
108
 
                }
109
 
 
110
 
    def save_object(self, req, data):
111
 
        if self.context is req.user:
112
 
            # Admin checkbox is disabled -- assume unchanged
113
 
            data['admin'] = self.context.admin
114
 
            data['disabled'] = self.context.state == u'disabled'
115
 
        else:
116
 
            self.context.admin = data['admin']
117
 
            if self.context.state in (u'enabled', u'disabled'):
118
 
                self.context.state = (u'disabled' if data['disabled']
119
 
                        else u'enabled')
120
 
        self.context.fullname = data['fullname'] \
121
 
                                if data['fullname'] else None
122
 
        self.context.studentid = data['studentid'] \
123
 
                                 if data['studentid'] else None
124
 
        return self.context
125
 
 
126
 
    def populate(self, req, ctx):
127
 
        super(UserAdminView, self).populate(req, ctx)
128
 
 
129
 
        # Disable the admin checkbox if editing oneself
130
 
        ctx['disable_admin'] = self.context is req.user
131
 
 
132
 
class PasswordChangeView(XHTMLView):
133
 
    """A form to change a user's password, with knowledge of the old one."""
134
 
    template = 'templates/user-password-change.html'
135
 
    tab = 'users'
136
 
    permission = 'edit'
137
 
 
138
 
    def authorize(self, req):
139
 
        """Only allow access if the requesting user holds the permission,
140
 
           and the target user has a password set. Otherwise we might be
141
 
           clobbering external authn.
142
 
        """
143
 
        return super(PasswordChangeView, self).authorize(req) and \
144
 
               self.context.passhash is not None
145
 
 
146
 
    def populate(self, req, ctx):
147
 
        error = None
148
 
        if req.method == 'POST':
149
 
            data = dict(req.get_fieldstorage())
150
 
            if data.get('old_password') is None or \
151
 
               not self.context.authenticate(data.get('old_password')):
152
 
                error = 'Incorrect password.'
153
 
            elif data.get('new_password') != data.get('new_password_again'):
154
 
                error = 'New passwords do not match.'
155
 
            elif not data.get('new_password'):
156
 
                error = 'New password cannot be empty.'
157
 
            else:
158
 
                self.context.password = data['new_password']
159
 
                req.store.commit()
160
 
                req.throw_redirect(req.uri)
161
 
 
162
 
        ctx['req'] = req
163
 
        ctx['user'] = self.context
164
 
        ctx['error'] = error
165
 
 
166
 
class PasswordResetView(XHTMLView):
167
 
    """A form to reset a user's password, without knowledge of the old one."""
168
 
    template = 'templates/user-password-reset.html'
169
 
    tab = 'users'
170
 
 
171
 
    def authorize(self, req):
172
 
        """Only allow access if the requesting user is an admin."""
173
 
        return req.user and req.user.admin
174
 
 
175
 
    def populate(self, req, ctx):
176
 
        error = None
177
 
        if req.method == 'POST':
178
 
            data = dict(req.get_fieldstorage())
179
 
            if data.get('new_password') != data.get('new_password_again'):
180
 
                error = 'New passwords do not match.'
181
 
            elif not data.get('new_password'):
182
 
                error = 'New password cannot be empty.'
183
 
            else:
184
 
                self.context.password = data['new_password']
185
 
                req.store.commit()
186
 
                req.throw_redirect(req.uri)
187
 
 
188
 
        ctx['user'] = self.context
189
 
        ctx['error'] = error
190
 
 
191
 
 
192
 
class UserNewSchema(formencode.Schema):
193
 
    login = URLNameValidator() # XXX: Validate uniqueness.
194
 
    admin = formencode.validators.StringBoolean(if_missing=False)
195
 
    fullname = formencode.validators.UnicodeString(not_empty=True)
196
 
    studentid = formencode.validators.UnicodeString(not_empty=False,
197
 
                                                    if_missing=None
198
 
                                                    )
199
 
    email = formencode.validators.Email(not_empty=False,
200
 
                                        if_missing=None)
201
 
 
202
 
 
203
 
class UserNewView(BaseFormView):
204
 
    """A form for admins to create new users."""
205
 
    template = 'templates/user-new.html'
206
 
    tab = 'users'
207
 
 
208
 
    def authorize(self, req):
209
 
        """Only allow access if the requesting user is an admin."""
210
 
        return req.user and req.user.admin
211
 
 
212
 
    @property
213
 
    def validator(self):
214
 
        return UserNewSchema()
215
 
 
216
 
    def get_default_data(self, req):
217
 
        return {}
218
 
 
219
 
    def save_object(self, req, data):
220
 
        data['nick'] = data['fullname']
221
 
        data['email'] = unicode(data['email']) if data['email'] else None
222
 
        userobj = User(**data)
223
 
        req.store.add(userobj)
224
 
        enrol_user(req.config, req.store, userobj)
225
 
 
226
 
        return userobj
227
 
 
228
 
 
229
 
class Plugin(ViewPlugin, MediaPlugin):
 
22
from ivle.webapp.base.plugins import ViewPlugin
 
23
from ivle.webapp.errors import NotFound, Unauthorized
 
24
import ivle.database
 
25
import ivle.util
 
26
 
 
27
# List of fields returned as part of the user JSON dictionary
 
28
# (as returned by the get_user action)
 
29
user_fields_list = (
 
30
    "login", "state", "unixid", "email", "nick", "fullname",
 
31
    "rolenm", "studentid", "acct_exp", "pass_exp", "last_login",
 
32
    "svn_pass"
 
33
)
 
34
 
 
35
class UserRESTView(JSONRESTView):
 
36
    """
 
37
    A REST interface to the user object.
 
38
    """
 
39
    def __init__(self, req, login):
 
40
        super(UserRESTView, self).__init__(self, req, login)
 
41
        self.context = ivle.database.User.get_by_login(req.store, login)
 
42
        if self.context is None:
 
43
            raise NotFound()
 
44
 
 
45
    def GET(self, req):
 
46
        # XXX Check Caps
 
47
        user = ivle.util.object_to_dict(user_fields_list, self.context)
 
48
        # Convert time stamps to nice strings
 
49
        for k in 'pass_exp', 'acct_exp', 'last_login':
 
50
            if user[k] is not None:
 
51
                user[k] = unicode(user[k])
 
52
 
 
53
        user['local_password'] = self.context.passhash is not None
 
54
        return user
 
55
 
 
56
    def PATCH(self, req, data):
 
57
        # XXX Check Caps
 
58
        # XXX Admins can set extra fields
 
59
        # Note: Cannot change password here (use change_password named op)
 
60
 
 
61
        for f in user_fields_list:
 
62
            try:
 
63
                field = data[f]
 
64
                if isinstance(field, str):
 
65
                    field = unicode(field)
 
66
                setattr(self.context, f, field)
 
67
            except KeyError:
 
68
                continue
 
69
 
 
70
class UserSettingsView(XHTMLView):
 
71
    template = 'user-settings.html'
 
72
    appname = 'settings'
 
73
 
 
74
    def __init__(self, req, login):
 
75
        self.context = ivle.database.User.get_by_login(req.store, login)
 
76
        if self.context is None:
 
77
            raise NotFound()
 
78
 
 
79
        if req.user is None or (req.user is not self.context and
 
80
                                req.user.rolenm != 'admin'):
 
81
            raise Unauthorized()
 
82
 
 
83
    def populate(self, req, ctx):
 
84
        self.plugin_scripts[Plugin] = ['settings.js']
 
85
        req.scripts_init = ['revert_settings']
 
86
 
 
87
        ctx['login'] = self.context.login
 
88
 
 
89
class Plugin(ViewPlugin):
230
90
    """
231
91
    The Plugin class for the user plugin.
232
92
    """
233
 
 
234
 
    forward_routes = (root_to_user,)
235
 
    reverse_routes = (user_url,)
236
 
    views = [(ApplicationRoot, ('users', '+index'), UsersView),
237
 
             (ApplicationRoot, ('users', '+new'), UserNewView),
238
 
             (User, '+index', UserEditView),
239
 
             (User, '+admin', UserAdminView),
240
 
             (User, '+changepassword', PasswordChangeView),
241
 
             (User, '+resetpassword', PasswordResetView),
242
 
             ]
243
 
 
244
 
    tabs = [
245
 
        ('users', 'Users', 'Display and edit all users',
246
 
         'users.png', 'users', 90, True)
 
93
    # Magic attribute: urls
 
94
    # Sequence of pairs/triples of
 
95
    # (regex str, handler class, kwargs dict)
 
96
    # The kwargs dict is passed to the __init__ of the view object
 
97
    urls = [
 
98
        ('~:login/+settings', UserSettingsView),
 
99
        ('api/~:login', UserRESTView),
247
100
    ]
248
101
 
249
 
    public_forward_routes = forward_routes
250
 
    public_reverse_routes = reverse_routes
251
 
 
252
102
    media = 'user-media'