##// END OF EJS Templates
Changed password crypting scheme to bcrypt, added dependency for setup
marcink -
r415:04e8b31f default
parent child Browse files
Show More
@@ -1,432 +1,433
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # authentication and permission libraries
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
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from pylons import config, session, url, request
27 27 from pylons.controllers.util import abort, redirect
28 28 from pylons_app.lib.utils import get_repo_slug
29 29 from pylons_app.model import meta
30 30 from pylons_app.model.db import User, RepoToPerm, Repository, Permission
31 31 from sqlalchemy.exc import OperationalError
32 32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 import hashlib
33 import bcrypt
34 34 from decorator import decorator
35 35 import logging
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 def get_crypt_password(password):
40 40 """Cryptographic function used for password hashing based on sha1
41 41 @param password: password to hash
42 42 """
43 hashed = hashlib.sha1(password).hexdigest()
44 return hashed[3:] + hashed[:3]
43 return bcrypt.hashpw(password, bcrypt.gensalt(10))
44
45 def check_password(password, hashed):
46 return bcrypt.hashpw(password, hashed) == hashed
45 47
46 48 @cache_region('super_short_term', 'cached_user')
47 49 def get_user_cached(username):
48 50 sa = meta.Session
49 51 try:
50 52 user = sa.query(User).filter(User.username == username).one()
51 53 finally:
52 54 meta.Session.remove()
53 55 return user
54 56
55 57 def authfunc(environ, username, password):
56 password_crypt = get_crypt_password(password)
57 58 try:
58 59 user = get_user_cached(username)
59 60 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
60 61 log.error(e)
61 62 user = None
62 63
63 64 if user:
64 65 if user.active:
65 if user.username == username and user.password == password_crypt:
66 if user.username == username and check_password(password, user.password):
66 67 log.info('user %s authenticated correctly', username)
67 68 return True
68 69 else:
69 70 log.error('user %s is disabled', username)
70 71
71 72 return False
72 73
73 74 class AuthUser(object):
74 75 """
75 76 A simple object that handles a mercurial username for authentication
76 77 """
77 78 def __init__(self):
78 79 self.username = 'None'
79 80 self.name = ''
80 81 self.lastname = ''
81 82 self.email = ''
82 83 self.user_id = None
83 84 self.is_authenticated = False
84 85 self.is_admin = False
85 86 self.permissions = {}
86 87
87 88
88 89 def set_available_permissions(config):
89 90 """
90 91 This function will propagate pylons globals with all available defined
91 92 permission given in db. We don't wannt to check each time from db for new
92 93 permissions since adding a new permission also requires application restart
93 94 ie. to decorate new views with the newly created permission
94 95 @param config:
95 96 """
96 97 log.info('getting information about all available permissions')
97 98 try:
98 99 sa = meta.Session
99 100 all_perms = sa.query(Permission).all()
100 101 finally:
101 102 meta.Session.remove()
102 103
103 104 config['available_permissions'] = [x.permission_name for x in all_perms]
104 105
105 106 def set_base_path(config):
106 107 config['base_path'] = config['pylons.app_globals'].base_path
107 108
108 109 def fill_data(user):
109 110 """
110 111 Fills user data with those from database and log out user if not present
111 112 in database
112 113 @param user:
113 114 """
114 115 sa = meta.Session
115 116 dbuser = sa.query(User).get(user.user_id)
116 117 if dbuser:
117 118 user.username = dbuser.username
118 119 user.is_admin = dbuser.admin
119 120 user.name = dbuser.name
120 121 user.lastname = dbuser.lastname
121 122 user.email = dbuser.email
122 123 else:
123 124 user.is_authenticated = False
124 125 meta.Session.remove()
125 126 return user
126 127
127 128 def fill_perms(user):
128 129 """
129 130 Fills user permission attribute with permissions taken from database
130 131 @param user:
131 132 """
132 133
133 134 sa = meta.Session
134 135 user.permissions['repositories'] = {}
135 136 user.permissions['global'] = set()
136 137
137 138 #first fetch default permissions
138 139 default_perms = sa.query(RepoToPerm, Repository, Permission)\
139 140 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
140 141 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
141 142 .filter(RepoToPerm.user_id == sa.query(User).filter(User.username ==
142 143 'default').one().user_id).all()
143 144
144 145 if user.is_admin:
145 146 user.permissions['global'].add('hg.admin')
146 147 #admin have all rights set to admin
147 148 for perm in default_perms:
148 149 p = 'repository.admin'
149 150 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
150 151
151 152 else:
152 153 user.permissions['global'].add('repository.create')
153 154 user.permissions['global'].add('hg.register')
154 155
155 156 for perm in default_perms:
156 157 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
157 158 #disable defaults for private repos,
158 159 p = 'repository.none'
159 160 elif perm.Repository.user_id == user.user_id:
160 161 #set admin if owner
161 162 p = 'repository.admin'
162 163 else:
163 164 p = perm.Permission.permission_name
164 165
165 166 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
166 167
167 168
168 169 user_perms = sa.query(RepoToPerm, Permission, Repository)\
169 170 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
170 171 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
171 172 .filter(RepoToPerm.user_id == user.user_id).all()
172 173 #overwrite userpermissions with defaults
173 174 for perm in user_perms:
174 175 #set write if owner
175 176 if perm.Repository.user_id == user.user_id:
176 177 p = 'repository.write'
177 178 else:
178 179 p = perm.Permission.permission_name
179 180 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
180 181 meta.Session.remove()
181 182 return user
182 183
183 184 def get_user(session):
184 185 """
185 186 Gets user from session, and wraps permissions into user
186 187 @param session:
187 188 """
188 189 user = session.get('hg_app_user', AuthUser())
189 190 if user.is_authenticated:
190 191 user = fill_data(user)
191 192 user = fill_perms(user)
192 193 session['hg_app_user'] = user
193 194 session.save()
194 195 return user
195 196
196 197 #===============================================================================
197 198 # CHECK DECORATORS
198 199 #===============================================================================
199 200 class LoginRequired(object):
200 201 """Must be logged in to execute this function else redirect to login page"""
201 202
202 203 def __call__(self, func):
203 204 return decorator(self.__wrapper, func)
204 205
205 206 def __wrapper(self, func, *fargs, **fkwargs):
206 207 user = session.get('hg_app_user', AuthUser())
207 208 log.debug('Checking login required for user:%s', user.username)
208 209 if user.is_authenticated:
209 210 log.debug('user %s is authenticated', user.username)
210 211 return func(*fargs, **fkwargs)
211 212 else:
212 213 log.warn('user %s not authenticated', user.username)
213 214 log.debug('redirecting to login page')
214 215 return redirect(url('login_home'))
215 216
216 217 class PermsDecorator(object):
217 218 """Base class for decorators"""
218 219
219 220 def __init__(self, *required_perms):
220 221 available_perms = config['available_permissions']
221 222 for perm in required_perms:
222 223 if perm not in available_perms:
223 224 raise Exception("'%s' permission is not defined" % perm)
224 225 self.required_perms = set(required_perms)
225 226 self.user_perms = None
226 227
227 228 def __call__(self, func):
228 229 return decorator(self.__wrapper, func)
229 230
230 231
231 232 def __wrapper(self, func, *fargs, **fkwargs):
232 233 # _wrapper.__name__ = func.__name__
233 234 # _wrapper.__dict__.update(func.__dict__)
234 235 # _wrapper.__doc__ = func.__doc__
235 236
236 237 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
237 238 log.debug('checking %s permissions %s for %s',
238 239 self.__class__.__name__, self.required_perms, func.__name__)
239 240
240 241 if self.check_permissions():
241 242 log.debug('Permission granted for %s', func.__name__)
242 243
243 244 return func(*fargs, **fkwargs)
244 245
245 246 else:
246 247 log.warning('Permission denied for %s', func.__name__)
247 248 #redirect with forbidden ret code
248 249 return abort(403)
249 250
250 251
251 252
252 253 def check_permissions(self):
253 254 """Dummy function for overriding"""
254 255 raise Exception('You have to write this function in child class')
255 256
256 257 class HasPermissionAllDecorator(PermsDecorator):
257 258 """Checks for access permission for all given predicates. All of them
258 259 have to be meet in order to fulfill the request
259 260 """
260 261
261 262 def check_permissions(self):
262 263 if self.required_perms.issubset(self.user_perms.get('global')):
263 264 return True
264 265 return False
265 266
266 267
267 268 class HasPermissionAnyDecorator(PermsDecorator):
268 269 """Checks for access permission for any of given predicates. In order to
269 270 fulfill the request any of predicates must be meet
270 271 """
271 272
272 273 def check_permissions(self):
273 274 if self.required_perms.intersection(self.user_perms.get('global')):
274 275 return True
275 276 return False
276 277
277 278 class HasRepoPermissionAllDecorator(PermsDecorator):
278 279 """Checks for access permission for all given predicates for specific
279 280 repository. All of them have to be meet in order to fulfill the request
280 281 """
281 282
282 283 def check_permissions(self):
283 284 repo_name = get_repo_slug(request)
284 285 try:
285 286 user_perms = set([self.user_perms['repositories'][repo_name]])
286 287 except KeyError:
287 288 return False
288 289 if self.required_perms.issubset(user_perms):
289 290 return True
290 291 return False
291 292
292 293
293 294 class HasRepoPermissionAnyDecorator(PermsDecorator):
294 295 """Checks for access permission for any of given predicates for specific
295 296 repository. In order to fulfill the request any of predicates must be meet
296 297 """
297 298
298 299 def check_permissions(self):
299 300 repo_name = get_repo_slug(request)
300 301
301 302 try:
302 303 user_perms = set([self.user_perms['repositories'][repo_name]])
303 304 except KeyError:
304 305 return False
305 306 if self.required_perms.intersection(user_perms):
306 307 return True
307 308 return False
308 309 #===============================================================================
309 310 # CHECK FUNCTIONS
310 311 #===============================================================================
311 312
312 313 class PermsFunction(object):
313 314 """Base function for other check functions"""
314 315
315 316 def __init__(self, *perms):
316 317 available_perms = config['available_permissions']
317 318
318 319 for perm in perms:
319 320 if perm not in available_perms:
320 321 raise Exception("'%s' permission in not defined" % perm)
321 322 self.required_perms = set(perms)
322 323 self.user_perms = None
323 324 self.granted_for = ''
324 325 self.repo_name = None
325 326
326 327 def __call__(self, check_Location=''):
327 328 user = session.get('hg_app_user', False)
328 329 if not user:
329 330 return False
330 331 self.user_perms = user.permissions
331 332 self.granted_for = user.username
332 333 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
333 334
334 335 if self.check_permissions():
335 336 log.debug('Permission granted for %s @%s', self.granted_for,
336 337 check_Location)
337 338 return True
338 339
339 340 else:
340 341 log.warning('Permission denied for %s @%s', self.granted_for,
341 342 check_Location)
342 343 return False
343 344
344 345 def check_permissions(self):
345 346 """Dummy function for overriding"""
346 347 raise Exception('You have to write this function in child class')
347 348
348 349 class HasPermissionAll(PermsFunction):
349 350 def check_permissions(self):
350 351 if self.required_perms.issubset(self.user_perms.get('global')):
351 352 return True
352 353 return False
353 354
354 355 class HasPermissionAny(PermsFunction):
355 356 def check_permissions(self):
356 357 if self.required_perms.intersection(self.user_perms.get('global')):
357 358 return True
358 359 return False
359 360
360 361 class HasRepoPermissionAll(PermsFunction):
361 362
362 363 def __call__(self, repo_name=None, check_Location=''):
363 364 self.repo_name = repo_name
364 365 return super(HasRepoPermissionAll, self).__call__(check_Location)
365 366
366 367 def check_permissions(self):
367 368 if not self.repo_name:
368 369 self.repo_name = get_repo_slug(request)
369 370
370 371 try:
371 372 self.user_perms = set([self.user_perms['repositories']\
372 373 [self.repo_name]])
373 374 except KeyError:
374 375 return False
375 376 self.granted_for = self.repo_name
376 377 if self.required_perms.issubset(self.user_perms):
377 378 return True
378 379 return False
379 380
380 381 class HasRepoPermissionAny(PermsFunction):
381 382
382 383 def __call__(self, repo_name=None, check_Location=''):
383 384 self.repo_name = repo_name
384 385 return super(HasRepoPermissionAny, self).__call__(check_Location)
385 386
386 387 def check_permissions(self):
387 388 if not self.repo_name:
388 389 self.repo_name = get_repo_slug(request)
389 390
390 391 try:
391 392 self.user_perms = set([self.user_perms['repositories']\
392 393 [self.repo_name]])
393 394 except KeyError:
394 395 return False
395 396 self.granted_for = self.repo_name
396 397 if self.required_perms.intersection(self.user_perms):
397 398 return True
398 399 return False
399 400
400 401 #===============================================================================
401 402 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
402 403 #===============================================================================
403 404
404 405 class HasPermissionAnyMiddleware(object):
405 406 def __init__(self, *perms):
406 407 self.required_perms = set(perms)
407 408
408 409 def __call__(self, user, repo_name):
409 410 usr = AuthUser()
410 411 usr.user_id = user.user_id
411 412 usr.username = user.username
412 413 usr.is_admin = user.admin
413 414
414 415 try:
415 416 self.user_perms = set([fill_perms(usr)\
416 417 .permissions['repositories'][repo_name]])
417 418 except:
418 419 self.user_perms = set()
419 420 self.granted_for = ''
420 421 self.username = user.username
421 422 self.repo_name = repo_name
422 423 return self.check_permissions()
423 424
424 425 def check_permissions(self):
425 426 log.debug('checking mercurial protocol '
426 427 'permissions for user:%s repository:%s',
427 428 self.username, self.repo_name)
428 429 if self.required_perms.intersection(self.user_perms):
429 430 log.debug('permission granted')
430 431 return True
431 432 log.debug('permission denied')
432 433 return False
@@ -1,330 +1,330
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 from pylons_app.lib.auth import get_crypt_password
27 from pylons_app.lib.auth import check_password
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.db import User, Repository
30 30 from sqlalchemy.exc import OperationalError
31 31 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
32 32 from webhelpers.pylonslib.secure_form import authentication_token
33 33 import datetime
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 sa = meta.Session
97 crypted_passwd = get_crypt_password(value['password'])
97 password = value['password']
98 98 username = value['username']
99 99 try:
100 100 user = sa.query(User).filter(User.username == username).one()
101 101 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
102 102 log.error(e)
103 103 user = None
104 104 raise formencode.Invalid(self.message('invalid_password',
105 105 state=State_obj), value, state,
106 106 error_dict=self.e_dict)
107 107 if user:
108 108 if user.active:
109 if user.username == username and user.password == crypted_passwd:
109 if user.username == username and check_password(password, user.password):
110 110 from pylons_app.lib.auth import AuthUser
111 111 auth_user = AuthUser()
112 112 auth_user.username = username
113 113 auth_user.is_authenticated = True
114 114 auth_user.is_admin = user.admin
115 115 auth_user.user_id = user.user_id
116 116 auth_user.name = user.name
117 117 auth_user.lastname = user.lastname
118 118 session['hg_app_user'] = auth_user
119 119 session.save()
120 120 log.info('user %s is now authenticated', username)
121 121
122 122 try:
123 123 user.last_login = datetime.datetime.now()
124 124 sa.add(user)
125 125 sa.commit()
126 126 except (OperationalError) as e:
127 127 log.error(e)
128 128 sa.rollback()
129 129
130 130 return value
131 131 else:
132 132 log.warning('user %s not authenticated', username)
133 133 raise formencode.Invalid(self.message('invalid_password',
134 134 state=State_obj), value, state,
135 135 error_dict=self.e_dict)
136 136 else:
137 137 log.warning('user %s is disabled', username)
138 138 raise formencode.Invalid(self.message('disabled_account',
139 139 state=State_obj),
140 140 value, state,
141 141 error_dict=self.e_dict_disable)
142 142
143 143 meta.Session.remove()
144 144
145 145
146 146 class ValidRepoUser(formencode.validators.FancyValidator):
147 147
148 148 def to_python(self, value, state):
149 149 sa = meta.Session
150 150 try:
151 151 self.user_db = sa.query(User)\
152 152 .filter(User.active == True)\
153 153 .filter(User.username == value).one()
154 154 except Exception:
155 155 raise formencode.Invalid(_('This username is not valid'),
156 156 value, state)
157 157 meta.Session.remove()
158 158 return self.user_db.user_id
159 159
160 160 def ValidRepoName(edit, old_data):
161 161 class _ValidRepoName(formencode.validators.FancyValidator):
162 162
163 163 def to_python(self, value, state):
164 164 slug = h.repo_name_slug(value)
165 165 if slug in ['_admin']:
166 166 raise formencode.Invalid(_('This repository name is disallowed'),
167 167 value, state)
168 168 if old_data.get('repo_name') != value or not edit:
169 169 sa = meta.Session
170 170 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
171 171 raise formencode.Invalid(_('This repository already exists') ,
172 172 value, state)
173 173 meta.Session.remove()
174 174 return slug
175 175
176 176
177 177 return _ValidRepoName
178 178
179 179 class ValidPerms(formencode.validators.FancyValidator):
180 180 messages = {'perm_new_user_name':_('This username is not valid')}
181 181
182 182 def to_python(self, value, state):
183 183 perms_update = []
184 184 perms_new = []
185 185 #build a list of permission to update and new permission to create
186 186 for k, v in value.items():
187 187 if k.startswith('perm_'):
188 188 if k.startswith('perm_new_user'):
189 189 new_perm = value.get('perm_new_user', False)
190 190 new_user = value.get('perm_new_user_name', False)
191 191 if new_user and new_perm:
192 192 if (new_user, new_perm) not in perms_new:
193 193 perms_new.append((new_user, new_perm))
194 194 else:
195 195 usr = k[5:]
196 196 if usr == 'default':
197 197 if value['private']:
198 198 #set none for default when updating to private repo
199 199 v = 'repository.none'
200 200 perms_update.append((usr, v))
201 201 value['perms_updates'] = perms_update
202 202 value['perms_new'] = perms_new
203 203 sa = meta.Session
204 204 for k, v in perms_new:
205 205 try:
206 206 self.user_db = sa.query(User)\
207 207 .filter(User.active == True)\
208 208 .filter(User.username == k).one()
209 209 except Exception:
210 210 msg = self.message('perm_new_user_name',
211 211 state=State_obj)
212 212 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
213 213 return value
214 214
215 215 class ValidSettings(formencode.validators.FancyValidator):
216 216
217 217 def to_python(self, value, state):
218 218 #settings form can't edit user
219 219 if value.has_key('user'):
220 220 del['value']['user']
221 221
222 222 return value
223 223
224 224 class ValidPath(formencode.validators.FancyValidator):
225 225 def to_python(self, value, state):
226 226 isdir = os.path.isdir(value.replace('*', ''))
227 227 if (value.endswith('/*') or value.endswith('/**')) and isdir:
228 228 return value
229 229 elif not isdir:
230 230 msg = _('This is not a valid path')
231 231 else:
232 232 msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)')
233 233
234 234 raise formencode.Invalid(msg, value, state,
235 235 error_dict={'paths_root_path':msg})
236 236
237 237 #===============================================================================
238 238 # FORMS
239 239 #===============================================================================
240 240 class LoginForm(formencode.Schema):
241 241 allow_extra_fields = True
242 242 filter_extra_fields = True
243 243 username = UnicodeString(
244 244 strip=True,
245 245 min=3,
246 246 not_empty=True,
247 247 messages={
248 248 'empty':_('Please enter a login'),
249 249 'tooShort':_('Enter a value %(min)i characters long or more')}
250 250 )
251 251
252 252 password = UnicodeString(
253 253 strip=True,
254 254 min=3,
255 255 not_empty=True,
256 256 messages={
257 257 'empty':_('Please enter a password'),
258 258 'tooShort':_('Enter a value %(min)i characters long or more')}
259 259 )
260 260
261 261
262 262 #chained validators have access to all data
263 263 chained_validators = [ValidAuth]
264 264
265 265 def UserForm(edit=False, old_data={}):
266 266 class _UserForm(formencode.Schema):
267 267 allow_extra_fields = True
268 268 filter_extra_fields = True
269 269 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
270 270 if edit:
271 271 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
272 272 admin = StringBoolean(if_missing=False)
273 273 else:
274 274 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
275 275 active = StringBoolean(if_missing=False)
276 276 name = UnicodeString(strip=True, min=3, not_empty=True)
277 277 lastname = UnicodeString(strip=True, min=3, not_empty=True)
278 278 email = Email(not_empty=True)
279 279
280 280 return _UserForm
281 281
282 282 RegisterForm = UserForm
283 283
284 284
285 285 def RepoForm(edit=False, old_data={}):
286 286 class _RepoForm(formencode.Schema):
287 287 allow_extra_fields = True
288 288 filter_extra_fields = False
289 289 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
290 290 description = UnicodeString(strip=True, min=3, not_empty=True)
291 291 private = StringBoolean(if_missing=False)
292 292
293 293 if edit:
294 294 user = All(Int(not_empty=True), ValidRepoUser)
295 295
296 296 chained_validators = [ValidPerms]
297 297 return _RepoForm
298 298
299 299 def RepoSettingsForm(edit=False, old_data={}):
300 300 class _RepoForm(formencode.Schema):
301 301 allow_extra_fields = True
302 302 filter_extra_fields = False
303 303 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
304 304 description = UnicodeString(strip=True, min=3, not_empty=True)
305 305 private = StringBoolean(if_missing=False)
306 306
307 307 chained_validators = [ValidPerms, ValidSettings]
308 308 return _RepoForm
309 309
310 310
311 311 def ApplicationSettingsForm():
312 312 class _ApplicationSettingsForm(formencode.Schema):
313 313 allow_extra_fields = True
314 314 filter_extra_fields = False
315 315 hg_app_title = UnicodeString(strip=True, min=3, not_empty=True)
316 316 hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True)
317 317
318 318 return _ApplicationSettingsForm
319 319
320 320 def ApplicationUiSettingsForm():
321 321 class _ApplicationUiSettingsForm(formencode.Schema):
322 322 allow_extra_fields = True
323 323 filter_extra_fields = False
324 324 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
325 325 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True))
326 326 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
327 327 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
328 328
329 329 return _ApplicationUiSettingsForm
330 330
@@ -1,46 +1,47
1 1 from pylons_app import get_version
2 2 try:
3 3 from setuptools import setup, find_packages
4 4 except ImportError:
5 5 from ez_setup import use_setuptools
6 6 use_setuptools()
7 7 from setuptools import setup, find_packages
8 8
9 9 setup(
10 10 name='hg_app',
11 11 version=get_version(),
12 12 description='Mercurial repository serving and browsing app',
13 13 keywords='mercurial web hgwebdir replacement serving hgweb',
14 14 license='BSD',
15 15 author='marcin kuzminski',
16 16 author_email='marcin@python-works.com',
17 17 url='http://hg.python-works.com',
18 18 install_requires=[
19 19 "Pylons>=1.0.0",
20 20 "SQLAlchemy>=0.6",
21 21 "Mako>=0.3.2",
22 22 "vcs>=0.1.4",
23 23 "pygments>=1.3.0",
24 24 "mercurial>=1.6",
25 25 "pysqlite",
26 26 "whoosh>=1.0.0b5",
27 "py-bcrypt",
27 28 ],
28 29 setup_requires=["PasteScript>=1.6.3"],
29 30 packages=find_packages(exclude=['ez_setup']),
30 31 include_package_data=True,
31 32 test_suite='nose.collector',
32 33 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
33 34 message_extractors={'pylons_app': [
34 35 ('**.py', 'python', None),
35 36 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
36 37 ('public/**', 'ignore', None)]},
37 38 zip_safe=False,
38 39 paster_plugins=['PasteScript', 'Pylons'],
39 40 entry_points="""
40 41 [paste.app_factory]
41 42 main = pylons_app.config.middleware:make_app
42 43
43 44 [paste.app_install]
44 45 main = pylons.util:PylonsInstaller
45 46 """,
46 47 )
General Comments 0
You need to be logged in to leave comments. Login now