##// END OF EJS Templates
fixes issue #78, ldap makes user validation caseInsensitive...
marcink -
r741:54684e07 beta
parent child Browse files
Show More
@@ -1,526 +1,534
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 pylons import config, session, url, request
26 26 from pylons.controllers.util import abort, redirect
27 27 from rhodecode.lib.exceptions import *
28 28 from rhodecode.lib.utils import get_repo_slug
29 29 from rhodecode.lib.auth_ldap import AuthLdap
30 30 from rhodecode.model import meta
31 31 from rhodecode.model.user import UserModel
32 32 from rhodecode.model.caching_query import FromCache
33 33 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
34 34 UserToPerm
35 35 import bcrypt
36 36 from decorator import decorator
37 37 import logging
38 38 import random
39 39 import traceback
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43 class PasswordGenerator(object):
44 44 """This is a simple class for generating password from
45 45 different sets of characters
46 46 usage:
47 47 passwd_gen = PasswordGenerator()
48 48 #print 8-letter password containing only big and small letters of alphabet
49 49 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
50 50 """
51 51 ALPHABETS_NUM = r'''1234567890'''#[0]
52 52 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
53 53 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
54 54 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
55 55 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
56 56 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
57 57 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
58 58 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
59 59 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
60 60
61 61 def __init__(self, passwd=''):
62 62 self.passwd = passwd
63 63
64 64 def gen_password(self, len, type):
65 65 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
66 66 return self.passwd
67 67
68 68
69 69 def get_crypt_password(password):
70 70 """Cryptographic function used for password hashing based on sha1
71 71 :param password: password to hash
72 72 """
73 73 return bcrypt.hashpw(password, bcrypt.gensalt(10))
74 74
75 75 def check_password(password, hashed):
76 76 return bcrypt.hashpw(password, hashed) == hashed
77 77
78 78 def authfunc(environ, username, password):
79 79 """
80 80 Authentication function used in Mercurial/Git/ and access control,
81 81 firstly checks for db authentication then if ldap is enabled for ldap
82 82 authentication, also creates ldap user if not in database
83 83
84 84 :param environ: needed only for using in Basic auth, can be None
85 85 :param username: username
86 86 :param password: password
87 87 """
88 88 user_model = UserModel()
89 89 user = user_model.get_by_username(username, cache=False)
90 90
91 91 if user is not None and user.is_ldap is False:
92 92 if user.active:
93 93
94 94 if user.username == 'default' and user.active:
95 95 log.info('user %s authenticated correctly', username)
96 96 return True
97 97
98 98 elif user.username == username and check_password(password, user.password):
99 99 log.info('user %s authenticated correctly', username)
100 100 return True
101 101 else:
102 102 log.error('user %s is disabled', username)
103 103
104 104
105 105 else:
106
107 #since ldap is searching in case insensitive check if this user is still
108 #not in our system
109 username = username.lower()
110 if user_model.get_by_username(username, cache=False) is not None:
111 return False
112
106 113 from rhodecode.model.settings import SettingsModel
107 114 ldap_settings = SettingsModel().get_ldap_settings()
108 115
109 116 #======================================================================
110 117 # FALLBACK TO LDAP AUTH IN ENABLE
111 118 #======================================================================
112 119 if ldap_settings.get('ldap_active', False):
120
113 121 kwargs = {
114 122 'server':ldap_settings.get('ldap_host', ''),
115 123 'base_dn':ldap_settings.get('ldap_base_dn', ''),
116 124 'port':ldap_settings.get('ldap_port'),
117 125 'bind_dn':ldap_settings.get('ldap_dn_user'),
118 126 'bind_pass':ldap_settings.get('ldap_dn_pass'),
119 127 'use_ldaps':ldap_settings.get('ldap_ldaps'),
120 128 'ldap_version':3,
121 129 }
122 130 log.debug('Checking for ldap authentication')
123 131 try:
124 132 aldap = AuthLdap(**kwargs)
125 133 res = aldap.authenticate_ldap(username, password)
126 134
127 135 authenticated = res[1]['uid'][0] == username
128 136
129 137 if authenticated and user_model.create_ldap(username, password):
130 138 log.info('created new ldap user')
131 139
132 140 return authenticated
133 141 except (LdapUsernameError, LdapPasswordError):
134 142 return False
135 143 except:
136 144 log.error(traceback.format_exc())
137 145 return False
138 146 return False
139 147
140 148 class AuthUser(object):
141 149 """
142 150 A simple object that handles a mercurial username for authentication
143 151 """
144 152 def __init__(self):
145 153 self.username = 'None'
146 154 self.name = ''
147 155 self.lastname = ''
148 156 self.email = ''
149 157 self.user_id = None
150 158 self.is_authenticated = False
151 159 self.is_admin = False
152 160 self.permissions = {}
153 161
154 162 def __repr__(self):
155 163 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
156 164
157 165 def set_available_permissions(config):
158 166 """
159 167 This function will propagate pylons globals with all available defined
160 168 permission given in db. We don't wannt to check each time from db for new
161 169 permissions since adding a new permission also requires application restart
162 170 ie. to decorate new views with the newly created permission
163 171 :param config:
164 172 """
165 173 log.info('getting information about all available permissions')
166 174 try:
167 175 sa = meta.Session()
168 176 all_perms = sa.query(Permission).all()
169 177 except:
170 178 pass
171 179 finally:
172 180 meta.Session.remove()
173 181
174 182 config['available_permissions'] = [x.permission_name for x in all_perms]
175 183
176 184 def set_base_path(config):
177 185 config['base_path'] = config['pylons.app_globals'].base_path
178 186
179 187
180 188 def fill_perms(user):
181 189 """
182 190 Fills user permission attribute with permissions taken from database
183 191 :param user:
184 192 """
185 193
186 194 sa = meta.Session()
187 195 user.permissions['repositories'] = {}
188 196 user.permissions['global'] = set()
189 197
190 198 #===========================================================================
191 199 # fetch default permissions
192 200 #===========================================================================
193 201 default_user = UserModel().get_by_username('default', cache=True)
194 202
195 203 default_perms = sa.query(RepoToPerm, Repository, Permission)\
196 204 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
197 205 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
198 206 .filter(RepoToPerm.user == default_user).all()
199 207
200 208 if user.is_admin:
201 209 #=======================================================================
202 210 # #admin have all default rights set to admin
203 211 #=======================================================================
204 212 user.permissions['global'].add('hg.admin')
205 213
206 214 for perm in default_perms:
207 215 p = 'repository.admin'
208 216 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
209 217
210 218 else:
211 219 #=======================================================================
212 220 # set default permissions
213 221 #=======================================================================
214 222
215 223 #default global
216 224 default_global_perms = sa.query(UserToPerm)\
217 .filter(UserToPerm.user == sa.query(User).filter(User.username ==
218 'default').one())
225 .filter(UserToPerm.user == sa.query(User)\
226 .filter(User.username == 'default').one())
219 227
220 228 for perm in default_global_perms:
221 229 user.permissions['global'].add(perm.permission.permission_name)
222 230
223 231 #default repositories
224 232 for perm in default_perms:
225 233 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
226 234 #disable defaults for private repos,
227 235 p = 'repository.none'
228 236 elif perm.Repository.user_id == user.user_id:
229 237 #set admin if owner
230 238 p = 'repository.admin'
231 239 else:
232 240 p = perm.Permission.permission_name
233 241
234 242 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
235 243
236 244 #=======================================================================
237 245 # #overwrite default with user permissions if any
238 246 #=======================================================================
239 247 user_perms = sa.query(RepoToPerm, Permission, Repository)\
240 248 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
241 249 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
242 250 .filter(RepoToPerm.user_id == user.user_id).all()
243 251
244 252 for perm in user_perms:
245 253 if perm.Repository.user_id == user.user_id:#set admin if owner
246 254 p = 'repository.admin'
247 255 else:
248 256 p = perm.Permission.permission_name
249 257 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
250 258 meta.Session.remove()
251 259 return user
252 260
253 261 def get_user(session):
254 262 """
255 263 Gets user from session, and wraps permissions into user
256 264 :param session:
257 265 """
258 266 user = session.get('rhodecode_user', AuthUser())
259 267 #if the user is not logged in we check for anonymous access
260 268 #if user is logged and it's a default user check if we still have anonymous
261 269 #access enabled
262 270 if user.user_id is None or user.username == 'default':
263 271 anonymous_user = UserModel().get_by_username('default', cache=True)
264 272 if anonymous_user.active is True:
265 273 #then we set this user is logged in
266 274 user.is_authenticated = True
267 275 user.user_id = anonymous_user.user_id
268 276 else:
269 277 user.is_authenticated = False
270 278
271 279 if user.is_authenticated:
272 280 user = UserModel().fill_data(user)
273 281
274 282 user = fill_perms(user)
275 283 session['rhodecode_user'] = user
276 284 session.save()
277 285 return user
278 286
279 287 #===============================================================================
280 288 # CHECK DECORATORS
281 289 #===============================================================================
282 290 class LoginRequired(object):
283 291 """Must be logged in to execute this function else redirect to login page"""
284 292
285 293 def __call__(self, func):
286 294 return decorator(self.__wrapper, func)
287 295
288 296 def __wrapper(self, func, *fargs, **fkwargs):
289 297 user = session.get('rhodecode_user', AuthUser())
290 298 log.debug('Checking login required for user:%s', user.username)
291 299 if user.is_authenticated:
292 300 log.debug('user %s is authenticated', user.username)
293 301 return func(*fargs, **fkwargs)
294 302 else:
295 303 log.warn('user %s not authenticated', user.username)
296 304
297 305 p = ''
298 306 if request.environ.get('SCRIPT_NAME') != '/':
299 307 p += request.environ.get('SCRIPT_NAME')
300 308
301 309 p += request.environ.get('PATH_INFO')
302 310 if request.environ.get('QUERY_STRING'):
303 311 p += '?' + request.environ.get('QUERY_STRING')
304 312
305 313 log.debug('redirecting to login page with %s', p)
306 314 return redirect(url('login_home', came_from=p))
307 315
308 316 class PermsDecorator(object):
309 317 """Base class for decorators"""
310 318
311 319 def __init__(self, *required_perms):
312 320 available_perms = config['available_permissions']
313 321 for perm in required_perms:
314 322 if perm not in available_perms:
315 323 raise Exception("'%s' permission is not defined" % perm)
316 324 self.required_perms = set(required_perms)
317 325 self.user_perms = None
318 326
319 327 def __call__(self, func):
320 328 return decorator(self.__wrapper, func)
321 329
322 330
323 331 def __wrapper(self, func, *fargs, **fkwargs):
324 332 # _wrapper.__name__ = func.__name__
325 333 # _wrapper.__dict__.update(func.__dict__)
326 334 # _wrapper.__doc__ = func.__doc__
327 335 self.user = session.get('rhodecode_user', AuthUser())
328 336 self.user_perms = self.user.permissions
329 337 log.debug('checking %s permissions %s for %s %s',
330 338 self.__class__.__name__, self.required_perms, func.__name__,
331 339 self.user)
332 340
333 341 if self.check_permissions():
334 342 log.debug('Permission granted for %s %s', func.__name__, self.user)
335 343
336 344 return func(*fargs, **fkwargs)
337 345
338 346 else:
339 347 log.warning('Permission denied for %s %s', func.__name__, self.user)
340 348 #redirect with forbidden ret code
341 349 return abort(403)
342 350
343 351
344 352
345 353 def check_permissions(self):
346 354 """Dummy function for overriding"""
347 355 raise Exception('You have to write this function in child class')
348 356
349 357 class HasPermissionAllDecorator(PermsDecorator):
350 358 """Checks for access permission for all given predicates. All of them
351 359 have to be meet in order to fulfill the request
352 360 """
353 361
354 362 def check_permissions(self):
355 363 if self.required_perms.issubset(self.user_perms.get('global')):
356 364 return True
357 365 return False
358 366
359 367
360 368 class HasPermissionAnyDecorator(PermsDecorator):
361 369 """Checks for access permission for any of given predicates. In order to
362 370 fulfill the request any of predicates must be meet
363 371 """
364 372
365 373 def check_permissions(self):
366 374 if self.required_perms.intersection(self.user_perms.get('global')):
367 375 return True
368 376 return False
369 377
370 378 class HasRepoPermissionAllDecorator(PermsDecorator):
371 379 """Checks for access permission for all given predicates for specific
372 380 repository. All of them have to be meet in order to fulfill the request
373 381 """
374 382
375 383 def check_permissions(self):
376 384 repo_name = get_repo_slug(request)
377 385 try:
378 386 user_perms = set([self.user_perms['repositories'][repo_name]])
379 387 except KeyError:
380 388 return False
381 389 if self.required_perms.issubset(user_perms):
382 390 return True
383 391 return False
384 392
385 393
386 394 class HasRepoPermissionAnyDecorator(PermsDecorator):
387 395 """Checks for access permission for any of given predicates for specific
388 396 repository. In order to fulfill the request any of predicates must be meet
389 397 """
390 398
391 399 def check_permissions(self):
392 400 repo_name = get_repo_slug(request)
393 401
394 402 try:
395 403 user_perms = set([self.user_perms['repositories'][repo_name]])
396 404 except KeyError:
397 405 return False
398 406 if self.required_perms.intersection(user_perms):
399 407 return True
400 408 return False
401 409 #===============================================================================
402 410 # CHECK FUNCTIONS
403 411 #===============================================================================
404 412
405 413 class PermsFunction(object):
406 414 """Base function for other check functions"""
407 415
408 416 def __init__(self, *perms):
409 417 available_perms = config['available_permissions']
410 418
411 419 for perm in perms:
412 420 if perm not in available_perms:
413 421 raise Exception("'%s' permission in not defined" % perm)
414 422 self.required_perms = set(perms)
415 423 self.user_perms = None
416 424 self.granted_for = ''
417 425 self.repo_name = None
418 426
419 427 def __call__(self, check_Location=''):
420 428 user = session.get('rhodecode_user', False)
421 429 if not user:
422 430 return False
423 431 self.user_perms = user.permissions
424 432 self.granted_for = user.username
425 433 log.debug('checking %s %s %s', self.__class__.__name__,
426 434 self.required_perms, user)
427 435
428 436 if self.check_permissions():
429 437 log.debug('Permission granted for %s @ %s %s', self.granted_for,
430 438 check_Location, user)
431 439 return True
432 440
433 441 else:
434 442 log.warning('Permission denied for %s @ %s %s', self.granted_for,
435 443 check_Location, user)
436 444 return False
437 445
438 446 def check_permissions(self):
439 447 """Dummy function for overriding"""
440 448 raise Exception('You have to write this function in child class')
441 449
442 450 class HasPermissionAll(PermsFunction):
443 451 def check_permissions(self):
444 452 if self.required_perms.issubset(self.user_perms.get('global')):
445 453 return True
446 454 return False
447 455
448 456 class HasPermissionAny(PermsFunction):
449 457 def check_permissions(self):
450 458 if self.required_perms.intersection(self.user_perms.get('global')):
451 459 return True
452 460 return False
453 461
454 462 class HasRepoPermissionAll(PermsFunction):
455 463
456 464 def __call__(self, repo_name=None, check_Location=''):
457 465 self.repo_name = repo_name
458 466 return super(HasRepoPermissionAll, self).__call__(check_Location)
459 467
460 468 def check_permissions(self):
461 469 if not self.repo_name:
462 470 self.repo_name = get_repo_slug(request)
463 471
464 472 try:
465 473 self.user_perms = set([self.user_perms['repositories']\
466 474 [self.repo_name]])
467 475 except KeyError:
468 476 return False
469 477 self.granted_for = self.repo_name
470 478 if self.required_perms.issubset(self.user_perms):
471 479 return True
472 480 return False
473 481
474 482 class HasRepoPermissionAny(PermsFunction):
475 483
476 484 def __call__(self, repo_name=None, check_Location=''):
477 485 self.repo_name = repo_name
478 486 return super(HasRepoPermissionAny, self).__call__(check_Location)
479 487
480 488 def check_permissions(self):
481 489 if not self.repo_name:
482 490 self.repo_name = get_repo_slug(request)
483 491
484 492 try:
485 493 self.user_perms = set([self.user_perms['repositories']\
486 494 [self.repo_name]])
487 495 except KeyError:
488 496 return False
489 497 self.granted_for = self.repo_name
490 498 if self.required_perms.intersection(self.user_perms):
491 499 return True
492 500 return False
493 501
494 502 #===============================================================================
495 503 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
496 504 #===============================================================================
497 505
498 506 class HasPermissionAnyMiddleware(object):
499 507 def __init__(self, *perms):
500 508 self.required_perms = set(perms)
501 509
502 510 def __call__(self, user, repo_name):
503 511 usr = AuthUser()
504 512 usr.user_id = user.user_id
505 513 usr.username = user.username
506 514 usr.is_admin = user.admin
507 515
508 516 try:
509 517 self.user_perms = set([fill_perms(usr)\
510 518 .permissions['repositories'][repo_name]])
511 519 except:
512 520 self.user_perms = set()
513 521 self.granted_for = ''
514 522 self.username = user.username
515 523 self.repo_name = repo_name
516 524 return self.check_permissions()
517 525
518 526 def check_permissions(self):
519 527 log.debug('checking mercurial protocol '
520 528 'permissions for user:%s repository:%s',
521 529 self.username, self.repo_name)
522 530 if self.required_perms.intersection(self.user_perms):
523 531 log.debug('permission granted')
524 532 return True
525 533 log.debug('permission denied')
526 534 return False
@@ -1,438 +1,441
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 rhodecode.lib.auth import authfunc, get_crypt_password
28 28 from rhodecode.lib.exceptions import LdapImportError
29 29 from rhodecode.model import meta
30 30 from rhodecode.model.user import UserModel
31 31 from rhodecode.model.repo import RepoModel
32 32 from rhodecode.model.db import User
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34 from rhodecode import BACKENDS
35 35 import formencode
36 36 import logging
37 37 import os
38 38 import rhodecode.lib.helpers as h
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 #this is needed to translate the messages using _() in validators
43 43 class State_obj(object):
44 44 _ = staticmethod(_)
45 45
46 46 #===============================================================================
47 47 # VALIDATORS
48 48 #===============================================================================
49 49 class ValidAuthToken(formencode.validators.FancyValidator):
50 50 messages = {'invalid_token':_('Token mismatch')}
51 51
52 52 def validate_python(self, value, state):
53 53
54 54 if value != authentication_token():
55 55 raise formencode.Invalid(self.message('invalid_token', state,
56 56 search_number=value), value, state)
57 57
58 58 def ValidUsername(edit, old_data):
59 59 class _ValidUsername(formencode.validators.FancyValidator):
60 60
61 61 def validate_python(self, value, state):
62 62 if value in ['default', 'new_user']:
63 63 raise formencode.Invalid(_('Invalid username'), value, state)
64 64 #check if user is unique
65 65 old_un = None
66 66 if edit:
67 67 old_un = UserModel().get(old_data.get('user_id')).username
68 68
69 69 if old_un != value or not edit:
70 if UserModel().get_by_username(value, cache=False):
70 if UserModel().get_by_username(value.lower(), cache=False):
71 71 raise formencode.Invalid(_('This username already exists') ,
72 72 value, state)
73 73
74 74 return _ValidUsername
75 75
76 76 class ValidPassword(formencode.validators.FancyValidator):
77 77
78 78 def to_python(self, value, state):
79 79
80 80 if value:
81 81
82 82 if value.get('password'):
83 83 try:
84 84 value['password'] = get_crypt_password(value['password'])
85 85 except UnicodeEncodeError:
86 86 e_dict = {'password':_('Invalid characters in password')}
87 87 raise formencode.Invalid('', value, state, error_dict=e_dict)
88 88
89 89 if value.get('password_confirmation'):
90 90 try:
91 91 value['password_confirmation'] = \
92 92 get_crypt_password(value['password_confirmation'])
93 93 except UnicodeEncodeError:
94 94 e_dict = {'password_confirmation':_('Invalid characters in password')}
95 95 raise formencode.Invalid('', value, state, error_dict=e_dict)
96 96
97 97 if value.get('new_password'):
98 98 try:
99 99 value['new_password'] = \
100 100 get_crypt_password(value['new_password'])
101 101 except UnicodeEncodeError:
102 102 e_dict = {'new_password':_('Invalid characters in password')}
103 103 raise formencode.Invalid('', value, state, error_dict=e_dict)
104 104
105 105 return value
106 106
107 107 class ValidPasswordsMatch(formencode.validators.FancyValidator):
108 108
109 109 def validate_python(self, value, state):
110 110
111 111 if value['password'] != value['password_confirmation']:
112 112 e_dict = {'password_confirmation':
113 113 _('Password do not match')}
114 114 raise formencode.Invalid('', value, state, error_dict=e_dict)
115 115
116 116 class ValidAuth(formencode.validators.FancyValidator):
117 117 messages = {
118 118 'invalid_password':_('invalid password'),
119 119 'invalid_login':_('invalid user name'),
120 120 'disabled_account':_('Your account is disabled')
121 121
122 122 }
123 123 #error mapping
124 124 e_dict = {'username':messages['invalid_login'],
125 125 'password':messages['invalid_password']}
126 126 e_dict_disable = {'username':messages['disabled_account']}
127 127
128 128 def validate_python(self, value, state):
129 129 password = value['password']
130 130 username = value['username']
131 131 user = UserModel().get_by_username(username)
132 132
133 133 if authfunc(None, username, password):
134 134 return value
135 135 else:
136 136 if user and user.active is False:
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 else:
143 143 log.warning('user %s not authenticated', username)
144 144 raise formencode.Invalid(self.message('invalid_password',
145 145 state=State_obj), value, state,
146 146 error_dict=self.e_dict)
147 147
148 148 class ValidRepoUser(formencode.validators.FancyValidator):
149 149
150 150 def to_python(self, value, state):
151 151 sa = meta.Session()
152 152 try:
153 153 self.user_db = sa.query(User)\
154 154 .filter(User.active == True)\
155 155 .filter(User.username == value).one()
156 156 except Exception:
157 157 raise formencode.Invalid(_('This username is not valid'),
158 158 value, state)
159 159 finally:
160 160 meta.Session.remove()
161 161
162 162 return self.user_db.user_id
163 163
164 164 def ValidRepoName(edit, old_data):
165 165 class _ValidRepoName(formencode.validators.FancyValidator):
166 166
167 167 def to_python(self, value, state):
168 168 slug = h.repo_name_slug(value)
169 169 if slug in ['_admin']:
170 170 raise formencode.Invalid(_('This repository name is disallowed'),
171 171 value, state)
172 172 if old_data.get('repo_name') != value or not edit:
173 173 if RepoModel().get_by_repo_name(slug, cache=False):
174 174 raise formencode.Invalid(_('This repository already exists') ,
175 175 value, state)
176 176 return slug
177 177
178 178
179 179 return _ValidRepoName
180 180
181 181 def ValidForkType(old_data):
182 182 class _ValidForkType(formencode.validators.FancyValidator):
183 183
184 184 def to_python(self, value, state):
185 185 if old_data['repo_type'] != value:
186 186 raise formencode.Invalid(_('Fork have to be the same type as original'), value, state)
187 187 return value
188 188 return _ValidForkType
189 189
190 190 class ValidPerms(formencode.validators.FancyValidator):
191 191 messages = {'perm_new_user_name':_('This username is not valid')}
192 192
193 193 def to_python(self, value, state):
194 194 perms_update = []
195 195 perms_new = []
196 196 #build a list of permission to update and new permission to create
197 197 for k, v in value.items():
198 198 if k.startswith('perm_'):
199 199 if k.startswith('perm_new_user'):
200 200 new_perm = value.get('perm_new_user', False)
201 201 new_user = value.get('perm_new_user_name', False)
202 202 if new_user and new_perm:
203 203 if (new_user, new_perm) not in perms_new:
204 204 perms_new.append((new_user, new_perm))
205 205 else:
206 206 usr = k[5:]
207 207 if usr == 'default':
208 208 if value['private']:
209 209 #set none for default when updating to private repo
210 210 v = 'repository.none'
211 211 perms_update.append((usr, v))
212 212 value['perms_updates'] = perms_update
213 213 value['perms_new'] = perms_new
214 214 sa = meta.Session
215 215 for k, v in perms_new:
216 216 try:
217 217 self.user_db = sa.query(User)\
218 218 .filter(User.active == True)\
219 219 .filter(User.username == k).one()
220 220 except Exception:
221 221 msg = self.message('perm_new_user_name',
222 222 state=State_obj)
223 223 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
224 224 return value
225 225
226 226 class ValidSettings(formencode.validators.FancyValidator):
227 227
228 228 def to_python(self, value, state):
229 229 #settings form can't edit user
230 230 if value.has_key('user'):
231 231 del['value']['user']
232 232
233 233 return value
234 234
235 235 class ValidPath(formencode.validators.FancyValidator):
236 236 def to_python(self, value, state):
237 237
238 238 if not os.path.isdir(value):
239 239 msg = _('This is not a valid path')
240 240 raise formencode.Invalid(msg, value, state,
241 241 error_dict={'paths_root_path':msg})
242 242 return value
243 243
244 244 def UniqSystemEmail(old_data):
245 245 class _UniqSystemEmail(formencode.validators.FancyValidator):
246 246 def to_python(self, value, state):
247 value = value.lower()
248 #TODO:write test for MixedCase scenarios
247 249 if old_data.get('email') != value:
248 250 sa = meta.Session()
249 251 try:
250 252 user = sa.query(User).filter(User.email == value).scalar()
251 253 if user:
252 254 raise formencode.Invalid(_("That e-mail address is already taken") ,
253 255 value, state)
254 256 finally:
255 257 meta.Session.remove()
256 258
257 259 return value
258 260
259 261 return _UniqSystemEmail
260 262
261 263 class ValidSystemEmail(formencode.validators.FancyValidator):
262 264 def to_python(self, value, state):
265 value = value.lower()
263 266 sa = meta.Session
264 267 try:
265 268 user = sa.query(User).filter(User.email == value).scalar()
266 269 if user is None:
267 270 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
268 271 value, state)
269 272 finally:
270 273 meta.Session.remove()
271 274
272 275 return value
273 276
274 277 class LdapLibValidator(formencode.validators.FancyValidator):
275 278
276 279 def to_python(self, value, state):
277 280
278 281 try:
279 282 import ldap
280 283 except ImportError:
281 284 raise LdapImportError
282 285 return value
283 286
284 287 #===============================================================================
285 288 # FORMS
286 289 #===============================================================================
287 290 class LoginForm(formencode.Schema):
288 291 allow_extra_fields = True
289 292 filter_extra_fields = True
290 293 username = UnicodeString(
291 294 strip=True,
292 295 min=1,
293 296 not_empty=True,
294 297 messages={
295 298 'empty':_('Please enter a login'),
296 299 'tooShort':_('Enter a value %(min)i characters long or more')}
297 300 )
298 301
299 302 password = UnicodeString(
300 303 strip=True,
301 304 min=6,
302 305 not_empty=True,
303 306 messages={
304 307 'empty':_('Please enter a password'),
305 308 'tooShort':_('Enter %(min)i characters or more')}
306 309 )
307 310
308 311
309 312 #chained validators have access to all data
310 313 chained_validators = [ValidAuth]
311 314
312 315 def UserForm(edit=False, old_data={}):
313 316 class _UserForm(formencode.Schema):
314 317 allow_extra_fields = True
315 318 filter_extra_fields = True
316 319 username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data))
317 320 if edit:
318 321 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
319 322 admin = StringBoolean(if_missing=False)
320 323 else:
321 324 password = All(UnicodeString(strip=True, min=6, not_empty=True))
322 325 active = StringBoolean(if_missing=False)
323 326 name = UnicodeString(strip=True, min=1, not_empty=True)
324 327 lastname = UnicodeString(strip=True, min=1, not_empty=True)
325 328 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
326 329
327 330 chained_validators = [ValidPassword]
328 331
329 332 return _UserForm
330 333
331 334 def RegisterForm(edit=False, old_data={}):
332 335 class _RegisterForm(formencode.Schema):
333 336 allow_extra_fields = True
334 337 filter_extra_fields = True
335 338 username = All(ValidUsername(edit, old_data), UnicodeString(strip=True, min=1, not_empty=True))
336 339 password = All(UnicodeString(strip=True, min=6, not_empty=True))
337 340 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
338 341 active = StringBoolean(if_missing=False)
339 342 name = UnicodeString(strip=True, min=1, not_empty=True)
340 343 lastname = UnicodeString(strip=True, min=1, not_empty=True)
341 344 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
342 345
343 346 chained_validators = [ValidPasswordsMatch, ValidPassword]
344 347
345 348 return _RegisterForm
346 349
347 350 def PasswordResetForm():
348 351 class _PasswordResetForm(formencode.Schema):
349 352 allow_extra_fields = True
350 353 filter_extra_fields = True
351 354 email = All(ValidSystemEmail(), Email(not_empty=True))
352 355 return _PasswordResetForm
353 356
354 357 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
355 358 class _RepoForm(formencode.Schema):
356 359 allow_extra_fields = True
357 360 filter_extra_fields = False
358 361 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
359 362 description = UnicodeString(strip=True, min=1, not_empty=True)
360 363 private = StringBoolean(if_missing=False)
361 364 repo_type = OneOf(supported_backends)
362 365 if edit:
363 366 user = All(Int(not_empty=True), ValidRepoUser)
364 367
365 368 chained_validators = [ValidPerms]
366 369 return _RepoForm
367 370
368 371 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
369 372 class _RepoForkForm(formencode.Schema):
370 373 allow_extra_fields = True
371 374 filter_extra_fields = False
372 375 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
373 376 description = UnicodeString(strip=True, min=1, not_empty=True)
374 377 private = StringBoolean(if_missing=False)
375 378 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
376 379 return _RepoForkForm
377 380
378 381 def RepoSettingsForm(edit=False, old_data={}):
379 382 class _RepoForm(formencode.Schema):
380 383 allow_extra_fields = True
381 384 filter_extra_fields = False
382 385 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
383 386 description = UnicodeString(strip=True, min=1, not_empty=True)
384 387 private = StringBoolean(if_missing=False)
385 388
386 389 chained_validators = [ValidPerms, ValidSettings]
387 390 return _RepoForm
388 391
389 392
390 393 def ApplicationSettingsForm():
391 394 class _ApplicationSettingsForm(formencode.Schema):
392 395 allow_extra_fields = True
393 396 filter_extra_fields = False
394 397 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
395 398 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
396 399
397 400 return _ApplicationSettingsForm
398 401
399 402 def ApplicationUiSettingsForm():
400 403 class _ApplicationUiSettingsForm(formencode.Schema):
401 404 allow_extra_fields = True
402 405 filter_extra_fields = False
403 406 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
404 407 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
405 408 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
406 409 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
407 410 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
408 411 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
409 412
410 413 return _ApplicationUiSettingsForm
411 414
412 415 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
413 416 class _DefaultPermissionsForm(formencode.Schema):
414 417 allow_extra_fields = True
415 418 filter_extra_fields = True
416 419 overwrite_default = StringBoolean(if_missing=False)
417 420 anonymous = OneOf(['True', 'False'], if_missing=False)
418 421 default_perm = OneOf(perms_choices)
419 422 default_register = OneOf(register_choices)
420 423 default_create = OneOf(create_choices)
421 424
422 425 return _DefaultPermissionsForm
423 426
424 427
425 428 def LdapSettingsForm():
426 429 class _LdapSettingsForm(formencode.Schema):
427 430 allow_extra_fields = True
428 431 filter_extra_fields = True
429 432 pre_validators = [LdapLibValidator]
430 433 ldap_active = StringBoolean(if_missing=False)
431 434 ldap_host = UnicodeString(strip=True,)
432 435 ldap_port = Number(strip=True,)
433 436 ldap_ldaps = StringBoolean(if_missing=False)
434 437 ldap_dn_user = UnicodeString(strip=True,)
435 438 ldap_dn_pass = UnicodeString(strip=True,)
436 439 ldap_base_dn = UnicodeString(strip=True,)
437 440
438 441 return _LdapSettingsForm
General Comments 0
You need to be logged in to leave comments. Login now