##// END OF EJS Templates
fixed validation of user email in user creation, and editing on admin panel
marcink -
r490:74b9bed2 celery
parent child Browse files
Show More
@@ -1,164 +1,166
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # users controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 4, 2010
22 22 users controller for pylons
23 23 @author: marcink
24 24 """
25 25
26 26 from formencode import htmlfill
27 27 from pylons import request, session, tmpl_context as c, url
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
32 32 from pylons_app.lib.base import BaseController, render
33 33 from pylons_app.model.db import User, UserLog
34 34 from pylons_app.model.forms import UserForm
35 35 from pylons_app.model.user_model import UserModel, DefaultUserException
36 36 import formencode
37 37 import logging
38 38 import traceback
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 class UsersController(BaseController):
43 43 """REST Controller styled on the Atom Publishing Protocol"""
44 44 # To properly map this controller, ensure your config/routing.py
45 45 # file has a resource setup:
46 46 # map.resource('user', 'users')
47 47
48 48 @LoginRequired()
49 49 @HasPermissionAllDecorator('hg.admin')
50 50 def __before__(self):
51 51 c.admin_user = session.get('admin_user')
52 52 c.admin_username = session.get('admin_username')
53 53 super(UsersController, self).__before__()
54 54
55 55
56 56 def index(self, format='html'):
57 57 """GET /users: All items in the collection"""
58 58 # url('users')
59 59
60 60 c.users_list = self.sa.query(User).all()
61 61 return render('admin/users/users.html')
62 62
63 63 def create(self):
64 64 """POST /users: Create a new item"""
65 65 # url('users')
66 66
67 67 user_model = UserModel()
68 68 login_form = UserForm()()
69 69 try:
70 70 form_result = login_form.to_python(dict(request.POST))
71 71 user_model.create(form_result)
72 72 h.flash(_('created user %s') % form_result['username'],
73 73 category='success')
74 74 except formencode.Invalid as errors:
75 75 return htmlfill.render(
76 76 render('admin/users/user_add.html'),
77 77 defaults=errors.value,
78 78 errors=errors.error_dict or {},
79 79 prefix_error=False,
80 80 encoding="UTF-8")
81 81 except Exception:
82 82 log.error(traceback.format_exc())
83 83 h.flash(_('error occured during creation of user %s') \
84 84 % request.POST.get('username'), category='error')
85 85 return redirect(url('users'))
86 86
87 87 def new(self, format='html'):
88 88 """GET /users/new: Form to create a new item"""
89 89 # url('new_user')
90 90 return render('admin/users/user_add.html')
91 91
92 92 def update(self, id):
93 93 """PUT /users/id: Update an existing item"""
94 94 # Forms posted to this method should contain a hidden field:
95 95 # <input type="hidden" name="_method" value="PUT" />
96 96 # Or using helpers:
97 97 # h.form(url('user', id=ID),
98 98 # method='put')
99 99 # url('user', id=ID)
100 100 user_model = UserModel()
101 _form = UserForm(edit=True, old_data={'user_id':id})()
101 c.user = user_model.get_user(id)
102
103 _form = UserForm(edit=True, old_data={'user_id':id,
104 'email':c.user.email})()
102 105 form_result = {}
103 106 try:
104 107 form_result = _form.to_python(dict(request.POST))
105 108 user_model.update(id, form_result)
106 109 h.flash(_('User updated succesfully'), category='success')
107 110
108 111 except formencode.Invalid as errors:
109 c.user = user_model.get_user(id)
110 112 return htmlfill.render(
111 113 render('admin/users/user_edit.html'),
112 114 defaults=errors.value,
113 115 errors=errors.error_dict or {},
114 116 prefix_error=False,
115 117 encoding="UTF-8")
116 118 except Exception:
117 119 log.error(traceback.format_exc())
118 120 h.flash(_('error occured during update of user %s') \
119 121 % form_result.get('username'), category='error')
120 122
121 123 return redirect(url('users'))
122 124
123 125 def delete(self, id):
124 126 """DELETE /users/id: Delete an existing item"""
125 127 # Forms posted to this method should contain a hidden field:
126 128 # <input type="hidden" name="_method" value="DELETE" />
127 129 # Or using helpers:
128 130 # h.form(url('user', id=ID),
129 131 # method='delete')
130 132 # url('user', id=ID)
131 133 user_model = UserModel()
132 134 try:
133 135 user_model.delete(id)
134 136 h.flash(_('sucessfully deleted user'), category='success')
135 137 except DefaultUserException as e:
136 138 h.flash(str(e), category='warning')
137 139 except Exception:
138 140 h.flash(_('An error occured during deletion of user'),
139 141 category='error')
140 142 return redirect(url('users'))
141 143
142 144 def show(self, id, format='html'):
143 145 """GET /users/id: Show a specific item"""
144 146 # url('user', id=ID)
145 147
146 148
147 149 def edit(self, id, format='html'):
148 150 """GET /users/id/edit: Form to edit an existing item"""
149 151 # url('edit_user', id=ID)
150 152 c.user = self.sa.query(User).get(id)
151 153 if not c.user:
152 154 return redirect(url('users'))
153 155 if c.user.username == 'default':
154 156 h.flash(_("You can't edit this user since it's"
155 157 " crucial for entire application"), category='warning')
156 158 return redirect(url('users'))
157 159
158 160 defaults = c.user.__dict__
159 161 return htmlfill.render(
160 162 render('admin/users/user_edit.html'),
161 163 defaults=defaults,
162 164 encoding="UTF-8",
163 165 force_defaults=False
164 166 )
@@ -1,351 +1,351
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 from formencode import All
23 23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 24 Email, Bool, StringBoolean
25 25 from pylons import session
26 26 from pylons.i18n.translation import _
27 27 from pylons_app.lib.auth import check_password, get_crypt_password
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.user_model import UserModel
30 30 from pylons_app.model.db import User, Repository
31 31 from sqlalchemy.exc import OperationalError
32 32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34 import formencode
35 35 import logging
36 36 import os
37 37 import pylons_app.lib.helpers as h
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 #this is needed to translate the messages using _() in validators
42 42 class State_obj(object):
43 43 _ = staticmethod(_)
44 44
45 45 #===============================================================================
46 46 # VALIDATORS
47 47 #===============================================================================
48 48 class ValidAuthToken(formencode.validators.FancyValidator):
49 49 messages = {'invalid_token':_('Token mismatch')}
50 50
51 51 def validate_python(self, value, state):
52 52
53 53 if value != authentication_token():
54 54 raise formencode.Invalid(self.message('invalid_token', state,
55 55 search_number=value), value, state)
56 56
57 57 def ValidUsername(edit, old_data):
58 58 class _ValidUsername(formencode.validators.FancyValidator):
59 59
60 60 def validate_python(self, value, state):
61 61 if value in ['default', 'new_user']:
62 62 raise formencode.Invalid(_('Invalid username'), value, state)
63 63 #check if user is uniq
64 64 sa = meta.Session
65 65 old_un = None
66 66 if edit:
67 67 old_un = sa.query(User).get(old_data.get('user_id')).username
68 68
69 69 if old_un != value or not edit:
70 70 if sa.query(User).filter(User.username == value).scalar():
71 71 raise formencode.Invalid(_('This username already exists') ,
72 72 value, state)
73 73 meta.Session.remove()
74 74
75 75 return _ValidUsername
76 76
77 77 class ValidPassword(formencode.validators.FancyValidator):
78 78
79 79 def to_python(self, value, state):
80 80 if value:
81 81 return get_crypt_password(value)
82 82
83 83 class ValidAuth(formencode.validators.FancyValidator):
84 84 messages = {
85 85 'invalid_password':_('invalid password'),
86 86 'invalid_login':_('invalid user name'),
87 87 'disabled_account':_('Your acccount is disabled')
88 88
89 89 }
90 90 #error mapping
91 91 e_dict = {'username':messages['invalid_login'],
92 92 'password':messages['invalid_password']}
93 93 e_dict_disable = {'username':messages['disabled_account']}
94 94
95 95 def validate_python(self, value, state):
96 96 password = value['password']
97 97 username = value['username']
98 98 user = UserModel().get_user_by_name(username)
99 99 if user is None:
100 100 raise formencode.Invalid(self.message('invalid_password',
101 101 state=State_obj), value, state,
102 102 error_dict=self.e_dict)
103 103 if user:
104 104 if user.active:
105 105 if user.username == username and check_password(password,
106 106 user.password):
107 107 return value
108 108 else:
109 109 log.warning('user %s not authenticated', username)
110 110 raise formencode.Invalid(self.message('invalid_password',
111 111 state=State_obj), value, state,
112 112 error_dict=self.e_dict)
113 113 else:
114 114 log.warning('user %s is disabled', username)
115 115 raise formencode.Invalid(self.message('disabled_account',
116 116 state=State_obj),
117 117 value, state,
118 118 error_dict=self.e_dict_disable)
119 119
120 120 class ValidRepoUser(formencode.validators.FancyValidator):
121 121
122 122 def to_python(self, value, state):
123 123 try:
124 124 self.user_db = meta.Session.query(User)\
125 125 .filter(User.active == True)\
126 126 .filter(User.username == value).one()
127 127 except Exception:
128 128 raise formencode.Invalid(_('This username is not valid'),
129 129 value, state)
130 130 finally:
131 131 meta.Session.remove()
132 132
133 133 return self.user_db.user_id
134 134
135 135 def ValidRepoName(edit, old_data):
136 136 class _ValidRepoName(formencode.validators.FancyValidator):
137 137
138 138 def to_python(self, value, state):
139 139 slug = h.repo_name_slug(value)
140 140 if slug in ['_admin']:
141 141 raise formencode.Invalid(_('This repository name is disallowed'),
142 142 value, state)
143 143 if old_data.get('repo_name') != value or not edit:
144 144 sa = meta.Session
145 145 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
146 146 raise formencode.Invalid(_('This repository already exists') ,
147 147 value, state)
148 148 meta.Session.remove()
149 149 return slug
150 150
151 151
152 152 return _ValidRepoName
153 153
154 154 class ValidPerms(formencode.validators.FancyValidator):
155 155 messages = {'perm_new_user_name':_('This username is not valid')}
156 156
157 157 def to_python(self, value, state):
158 158 perms_update = []
159 159 perms_new = []
160 160 #build a list of permission to update and new permission to create
161 161 for k, v in value.items():
162 162 if k.startswith('perm_'):
163 163 if k.startswith('perm_new_user'):
164 164 new_perm = value.get('perm_new_user', False)
165 165 new_user = value.get('perm_new_user_name', False)
166 166 if new_user and new_perm:
167 167 if (new_user, new_perm) not in perms_new:
168 168 perms_new.append((new_user, new_perm))
169 169 else:
170 170 usr = k[5:]
171 171 if usr == 'default':
172 172 if value['private']:
173 173 #set none for default when updating to private repo
174 174 v = 'repository.none'
175 175 perms_update.append((usr, v))
176 176 value['perms_updates'] = perms_update
177 177 value['perms_new'] = perms_new
178 178 sa = meta.Session
179 179 for k, v in perms_new:
180 180 try:
181 181 self.user_db = sa.query(User)\
182 182 .filter(User.active == True)\
183 183 .filter(User.username == k).one()
184 184 except Exception:
185 185 msg = self.message('perm_new_user_name',
186 186 state=State_obj)
187 187 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
188 188 return value
189 189
190 190 class ValidSettings(formencode.validators.FancyValidator):
191 191
192 192 def to_python(self, value, state):
193 193 #settings form can't edit user
194 194 if value.has_key('user'):
195 195 del['value']['user']
196 196
197 197 return value
198 198
199 199 class ValidPath(formencode.validators.FancyValidator):
200 200 def to_python(self, value, state):
201 201 isdir = os.path.isdir(value.replace('*', ''))
202 202 if (value.endswith('/*') or value.endswith('/**')) and isdir:
203 203 return value
204 204 elif not isdir:
205 205 msg = _('This is not a valid path')
206 206 else:
207 207 msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)')
208 208
209 209 raise formencode.Invalid(msg, value, state,
210 210 error_dict={'paths_root_path':msg})
211 211
212 212 def UniqSystemEmail(old_data):
213 213 class _UniqSystemEmail(formencode.validators.FancyValidator):
214 214 def to_python(self, value, state):
215 if old_data['email'] != value:
215 if old_data.get('email') != value:
216 216 sa = meta.Session
217 217 try:
218 218 user = sa.query(User).filter(User.email == value).scalar()
219 219 if user:
220 220 raise formencode.Invalid(_("That e-mail address is already taken") ,
221 221 value, state)
222 222 finally:
223 223 meta.Session.remove()
224 224
225 225 return value
226 226
227 227 return _UniqSystemEmail
228 228
229 229 class ValidSystemEmail(formencode.validators.FancyValidator):
230 230 def to_python(self, value, state):
231 231 sa = meta.Session
232 232 try:
233 233 user = sa.query(User).filter(User.email == value).scalar()
234 234 if user is None:
235 235 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
236 236 value, state)
237 237 finally:
238 238 meta.Session.remove()
239 239
240 240 return value
241 241
242 242 #===============================================================================
243 243 # FORMS
244 244 #===============================================================================
245 245 class LoginForm(formencode.Schema):
246 246 allow_extra_fields = True
247 247 filter_extra_fields = True
248 248 username = UnicodeString(
249 249 strip=True,
250 250 min=3,
251 251 not_empty=True,
252 252 messages={
253 253 'empty':_('Please enter a login'),
254 254 'tooShort':_('Enter a value %(min)i characters long or more')}
255 255 )
256 256
257 257 password = UnicodeString(
258 258 strip=True,
259 259 min=3,
260 260 not_empty=True,
261 261 messages={
262 262 'empty':_('Please enter a password'),
263 263 'tooShort':_('Enter a value %(min)i characters long or more')}
264 264 )
265 265
266 266
267 267 #chained validators have access to all data
268 268 chained_validators = [ValidAuth]
269 269
270 270 def UserForm(edit=False, old_data={}):
271 271 class _UserForm(formencode.Schema):
272 272 allow_extra_fields = True
273 273 filter_extra_fields = True
274 274 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
275 275 if edit:
276 276 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
277 277 admin = StringBoolean(if_missing=False)
278 278 else:
279 279 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
280 280 active = StringBoolean(if_missing=False)
281 281 name = UnicodeString(strip=True, min=3, not_empty=True)
282 282 lastname = UnicodeString(strip=True, min=3, not_empty=True)
283 283 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
284 284
285 285 return _UserForm
286 286
287 287 RegisterForm = UserForm
288 288
289 289 def PasswordResetForm():
290 290 class _PasswordResetForm(formencode.Schema):
291 291 allow_extra_fields = True
292 292 filter_extra_fields = True
293 293 email = All(ValidSystemEmail(), Email(not_empty=True))
294 294 return _PasswordResetForm
295 295
296 296 def RepoForm(edit=False, old_data={}):
297 297 class _RepoForm(formencode.Schema):
298 298 allow_extra_fields = True
299 299 filter_extra_fields = False
300 300 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
301 301 description = UnicodeString(strip=True, min=3, not_empty=True)
302 302 private = StringBoolean(if_missing=False)
303 303
304 304 if edit:
305 305 user = All(Int(not_empty=True), ValidRepoUser)
306 306
307 307 chained_validators = [ValidPerms]
308 308 return _RepoForm
309 309
310 310 def RepoSettingsForm(edit=False, old_data={}):
311 311 class _RepoForm(formencode.Schema):
312 312 allow_extra_fields = True
313 313 filter_extra_fields = False
314 314 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
315 315 description = UnicodeString(strip=True, min=3, not_empty=True)
316 316 private = StringBoolean(if_missing=False)
317 317
318 318 chained_validators = [ValidPerms, ValidSettings]
319 319 return _RepoForm
320 320
321 321
322 322 def ApplicationSettingsForm():
323 323 class _ApplicationSettingsForm(formencode.Schema):
324 324 allow_extra_fields = True
325 325 filter_extra_fields = False
326 326 hg_app_title = UnicodeString(strip=True, min=3, not_empty=True)
327 327 hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True)
328 328
329 329 return _ApplicationSettingsForm
330 330
331 331 def ApplicationUiSettingsForm():
332 332 class _ApplicationUiSettingsForm(formencode.Schema):
333 333 allow_extra_fields = True
334 334 filter_extra_fields = False
335 335 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
336 336 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True))
337 337 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
338 338 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
339 339
340 340 return _ApplicationUiSettingsForm
341 341
342 342 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
343 343 class _DefaultPermissionsForm(formencode.Schema):
344 344 allow_extra_fields = True
345 345 filter_extra_fields = True
346 346 overwrite_default = OneOf(['true', 'false'], if_missing='false')
347 347 default_perm = OneOf(perms_choices)
348 348 default_register = OneOf(register_choices)
349 349 default_create = OneOf(create_choices)
350 350
351 351 return _DefaultPermissionsForm
General Comments 0
You need to be logged in to leave comments. Login now