##// END OF EJS Templates
added debug message for ldap auth
marcink -
r749:fcd4fb51 beta
parent child Browse files
Show More
@@ -1,536 +1,537
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 106
107 107 #since ldap is searching in case insensitive check if this user is still
108 108 #not in our system
109 109 username = username.lower()
110 110 user_obj = user_model.get_by_username(username, cache=False,
111 111 case_insensitive=True)
112 112 if user_obj is not None and user_obj.is_ldap is False:
113 log.debug('this user already exists as non ldap')
113 114 return False
114 115
115 116 from rhodecode.model.settings import SettingsModel
116 117 ldap_settings = SettingsModel().get_ldap_settings()
117 118
118 119 #======================================================================
119 120 # FALLBACK TO LDAP AUTH IN ENABLE
120 121 #======================================================================
121 122 if ldap_settings.get('ldap_active', False):
122 123
123 124 kwargs = {
124 125 'server':ldap_settings.get('ldap_host', ''),
125 126 'base_dn':ldap_settings.get('ldap_base_dn', ''),
126 127 'port':ldap_settings.get('ldap_port'),
127 128 'bind_dn':ldap_settings.get('ldap_dn_user'),
128 129 'bind_pass':ldap_settings.get('ldap_dn_pass'),
129 130 'use_ldaps':ldap_settings.get('ldap_ldaps'),
130 131 'ldap_version':3,
131 132 }
132 133 log.debug('Checking for ldap authentication')
133 134 try:
134 135 aldap = AuthLdap(**kwargs)
135 136 res = aldap.authenticate_ldap(username, password)
136 137
137 138 authenticated = res[1]['uid'][0] == username
138 139
139 140 if authenticated and user_model.create_ldap(username, password):
140 141 log.info('created new ldap user')
141 142
142 143 return authenticated
143 144 except (LdapUsernameError, LdapPasswordError):
144 145 return False
145 146 except:
146 147 log.error(traceback.format_exc())
147 148 return False
148 149 return False
149 150
150 151 class AuthUser(object):
151 152 """
152 153 A simple object that handles a mercurial username for authentication
153 154 """
154 155 def __init__(self):
155 156 self.username = 'None'
156 157 self.name = ''
157 158 self.lastname = ''
158 159 self.email = ''
159 160 self.user_id = None
160 161 self.is_authenticated = False
161 162 self.is_admin = False
162 163 self.permissions = {}
163 164
164 165 def __repr__(self):
165 166 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
166 167
167 168 def set_available_permissions(config):
168 169 """
169 170 This function will propagate pylons globals with all available defined
170 171 permission given in db. We don't wannt to check each time from db for new
171 172 permissions since adding a new permission also requires application restart
172 173 ie. to decorate new views with the newly created permission
173 174 :param config:
174 175 """
175 176 log.info('getting information about all available permissions')
176 177 try:
177 178 sa = meta.Session()
178 179 all_perms = sa.query(Permission).all()
179 180 except:
180 181 pass
181 182 finally:
182 183 meta.Session.remove()
183 184
184 185 config['available_permissions'] = [x.permission_name for x in all_perms]
185 186
186 187 def set_base_path(config):
187 188 config['base_path'] = config['pylons.app_globals'].base_path
188 189
189 190
190 191 def fill_perms(user):
191 192 """
192 193 Fills user permission attribute with permissions taken from database
193 194 :param user:
194 195 """
195 196
196 197 sa = meta.Session()
197 198 user.permissions['repositories'] = {}
198 199 user.permissions['global'] = set()
199 200
200 201 #===========================================================================
201 202 # fetch default permissions
202 203 #===========================================================================
203 204 default_user = UserModel().get_by_username('default', cache=True)
204 205
205 206 default_perms = sa.query(RepoToPerm, Repository, Permission)\
206 207 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
207 208 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
208 209 .filter(RepoToPerm.user == default_user).all()
209 210
210 211 if user.is_admin:
211 212 #=======================================================================
212 213 # #admin have all default rights set to admin
213 214 #=======================================================================
214 215 user.permissions['global'].add('hg.admin')
215 216
216 217 for perm in default_perms:
217 218 p = 'repository.admin'
218 219 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
219 220
220 221 else:
221 222 #=======================================================================
222 223 # set default permissions
223 224 #=======================================================================
224 225
225 226 #default global
226 227 default_global_perms = sa.query(UserToPerm)\
227 228 .filter(UserToPerm.user == sa.query(User)\
228 229 .filter(User.username == 'default').one())
229 230
230 231 for perm in default_global_perms:
231 232 user.permissions['global'].add(perm.permission.permission_name)
232 233
233 234 #default repositories
234 235 for perm in default_perms:
235 236 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
236 237 #disable defaults for private repos,
237 238 p = 'repository.none'
238 239 elif perm.Repository.user_id == user.user_id:
239 240 #set admin if owner
240 241 p = 'repository.admin'
241 242 else:
242 243 p = perm.Permission.permission_name
243 244
244 245 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
245 246
246 247 #=======================================================================
247 248 # #overwrite default with user permissions if any
248 249 #=======================================================================
249 250 user_perms = sa.query(RepoToPerm, Permission, Repository)\
250 251 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
251 252 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
252 253 .filter(RepoToPerm.user_id == user.user_id).all()
253 254
254 255 for perm in user_perms:
255 256 if perm.Repository.user_id == user.user_id:#set admin if owner
256 257 p = 'repository.admin'
257 258 else:
258 259 p = perm.Permission.permission_name
259 260 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
260 261 meta.Session.remove()
261 262 return user
262 263
263 264 def get_user(session):
264 265 """
265 266 Gets user from session, and wraps permissions into user
266 267 :param session:
267 268 """
268 269 user = session.get('rhodecode_user', AuthUser())
269 270 #if the user is not logged in we check for anonymous access
270 271 #if user is logged and it's a default user check if we still have anonymous
271 272 #access enabled
272 273 if user.user_id is None or user.username == 'default':
273 274 anonymous_user = UserModel().get_by_username('default', cache=True)
274 275 if anonymous_user.active is True:
275 276 #then we set this user is logged in
276 277 user.is_authenticated = True
277 278 user.user_id = anonymous_user.user_id
278 279 else:
279 280 user.is_authenticated = False
280 281
281 282 if user.is_authenticated:
282 283 user = UserModel().fill_data(user)
283 284
284 285 user = fill_perms(user)
285 286 session['rhodecode_user'] = user
286 287 session.save()
287 288 return user
288 289
289 290 #===============================================================================
290 291 # CHECK DECORATORS
291 292 #===============================================================================
292 293 class LoginRequired(object):
293 294 """Must be logged in to execute this function else redirect to login page"""
294 295
295 296 def __call__(self, func):
296 297 return decorator(self.__wrapper, func)
297 298
298 299 def __wrapper(self, func, *fargs, **fkwargs):
299 300 user = session.get('rhodecode_user', AuthUser())
300 301 log.debug('Checking login required for user:%s', user.username)
301 302 if user.is_authenticated:
302 303 log.debug('user %s is authenticated', user.username)
303 304 return func(*fargs, **fkwargs)
304 305 else:
305 306 log.warn('user %s not authenticated', user.username)
306 307
307 308 p = ''
308 309 if request.environ.get('SCRIPT_NAME') != '/':
309 310 p += request.environ.get('SCRIPT_NAME')
310 311
311 312 p += request.environ.get('PATH_INFO')
312 313 if request.environ.get('QUERY_STRING'):
313 314 p += '?' + request.environ.get('QUERY_STRING')
314 315
315 316 log.debug('redirecting to login page with %s', p)
316 317 return redirect(url('login_home', came_from=p))
317 318
318 319 class PermsDecorator(object):
319 320 """Base class for decorators"""
320 321
321 322 def __init__(self, *required_perms):
322 323 available_perms = config['available_permissions']
323 324 for perm in required_perms:
324 325 if perm not in available_perms:
325 326 raise Exception("'%s' permission is not defined" % perm)
326 327 self.required_perms = set(required_perms)
327 328 self.user_perms = None
328 329
329 330 def __call__(self, func):
330 331 return decorator(self.__wrapper, func)
331 332
332 333
333 334 def __wrapper(self, func, *fargs, **fkwargs):
334 335 # _wrapper.__name__ = func.__name__
335 336 # _wrapper.__dict__.update(func.__dict__)
336 337 # _wrapper.__doc__ = func.__doc__
337 338 self.user = session.get('rhodecode_user', AuthUser())
338 339 self.user_perms = self.user.permissions
339 340 log.debug('checking %s permissions %s for %s %s',
340 341 self.__class__.__name__, self.required_perms, func.__name__,
341 342 self.user)
342 343
343 344 if self.check_permissions():
344 345 log.debug('Permission granted for %s %s', func.__name__, self.user)
345 346
346 347 return func(*fargs, **fkwargs)
347 348
348 349 else:
349 350 log.warning('Permission denied for %s %s', func.__name__, self.user)
350 351 #redirect with forbidden ret code
351 352 return abort(403)
352 353
353 354
354 355
355 356 def check_permissions(self):
356 357 """Dummy function for overriding"""
357 358 raise Exception('You have to write this function in child class')
358 359
359 360 class HasPermissionAllDecorator(PermsDecorator):
360 361 """Checks for access permission for all given predicates. All of them
361 362 have to be meet in order to fulfill the request
362 363 """
363 364
364 365 def check_permissions(self):
365 366 if self.required_perms.issubset(self.user_perms.get('global')):
366 367 return True
367 368 return False
368 369
369 370
370 371 class HasPermissionAnyDecorator(PermsDecorator):
371 372 """Checks for access permission for any of given predicates. In order to
372 373 fulfill the request any of predicates must be meet
373 374 """
374 375
375 376 def check_permissions(self):
376 377 if self.required_perms.intersection(self.user_perms.get('global')):
377 378 return True
378 379 return False
379 380
380 381 class HasRepoPermissionAllDecorator(PermsDecorator):
381 382 """Checks for access permission for all given predicates for specific
382 383 repository. All of them have to be meet in order to fulfill the request
383 384 """
384 385
385 386 def check_permissions(self):
386 387 repo_name = get_repo_slug(request)
387 388 try:
388 389 user_perms = set([self.user_perms['repositories'][repo_name]])
389 390 except KeyError:
390 391 return False
391 392 if self.required_perms.issubset(user_perms):
392 393 return True
393 394 return False
394 395
395 396
396 397 class HasRepoPermissionAnyDecorator(PermsDecorator):
397 398 """Checks for access permission for any of given predicates for specific
398 399 repository. In order to fulfill the request any of predicates must be meet
399 400 """
400 401
401 402 def check_permissions(self):
402 403 repo_name = get_repo_slug(request)
403 404
404 405 try:
405 406 user_perms = set([self.user_perms['repositories'][repo_name]])
406 407 except KeyError:
407 408 return False
408 409 if self.required_perms.intersection(user_perms):
409 410 return True
410 411 return False
411 412 #===============================================================================
412 413 # CHECK FUNCTIONS
413 414 #===============================================================================
414 415
415 416 class PermsFunction(object):
416 417 """Base function for other check functions"""
417 418
418 419 def __init__(self, *perms):
419 420 available_perms = config['available_permissions']
420 421
421 422 for perm in perms:
422 423 if perm not in available_perms:
423 424 raise Exception("'%s' permission in not defined" % perm)
424 425 self.required_perms = set(perms)
425 426 self.user_perms = None
426 427 self.granted_for = ''
427 428 self.repo_name = None
428 429
429 430 def __call__(self, check_Location=''):
430 431 user = session.get('rhodecode_user', False)
431 432 if not user:
432 433 return False
433 434 self.user_perms = user.permissions
434 435 self.granted_for = user.username
435 436 log.debug('checking %s %s %s', self.__class__.__name__,
436 437 self.required_perms, user)
437 438
438 439 if self.check_permissions():
439 440 log.debug('Permission granted for %s @ %s %s', self.granted_for,
440 441 check_Location, user)
441 442 return True
442 443
443 444 else:
444 445 log.warning('Permission denied for %s @ %s %s', self.granted_for,
445 446 check_Location, user)
446 447 return False
447 448
448 449 def check_permissions(self):
449 450 """Dummy function for overriding"""
450 451 raise Exception('You have to write this function in child class')
451 452
452 453 class HasPermissionAll(PermsFunction):
453 454 def check_permissions(self):
454 455 if self.required_perms.issubset(self.user_perms.get('global')):
455 456 return True
456 457 return False
457 458
458 459 class HasPermissionAny(PermsFunction):
459 460 def check_permissions(self):
460 461 if self.required_perms.intersection(self.user_perms.get('global')):
461 462 return True
462 463 return False
463 464
464 465 class HasRepoPermissionAll(PermsFunction):
465 466
466 467 def __call__(self, repo_name=None, check_Location=''):
467 468 self.repo_name = repo_name
468 469 return super(HasRepoPermissionAll, self).__call__(check_Location)
469 470
470 471 def check_permissions(self):
471 472 if not self.repo_name:
472 473 self.repo_name = get_repo_slug(request)
473 474
474 475 try:
475 476 self.user_perms = set([self.user_perms['repositories']\
476 477 [self.repo_name]])
477 478 except KeyError:
478 479 return False
479 480 self.granted_for = self.repo_name
480 481 if self.required_perms.issubset(self.user_perms):
481 482 return True
482 483 return False
483 484
484 485 class HasRepoPermissionAny(PermsFunction):
485 486
486 487 def __call__(self, repo_name=None, check_Location=''):
487 488 self.repo_name = repo_name
488 489 return super(HasRepoPermissionAny, self).__call__(check_Location)
489 490
490 491 def check_permissions(self):
491 492 if not self.repo_name:
492 493 self.repo_name = get_repo_slug(request)
493 494
494 495 try:
495 496 self.user_perms = set([self.user_perms['repositories']\
496 497 [self.repo_name]])
497 498 except KeyError:
498 499 return False
499 500 self.granted_for = self.repo_name
500 501 if self.required_perms.intersection(self.user_perms):
501 502 return True
502 503 return False
503 504
504 505 #===============================================================================
505 506 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
506 507 #===============================================================================
507 508
508 509 class HasPermissionAnyMiddleware(object):
509 510 def __init__(self, *perms):
510 511 self.required_perms = set(perms)
511 512
512 513 def __call__(self, user, repo_name):
513 514 usr = AuthUser()
514 515 usr.user_id = user.user_id
515 516 usr.username = user.username
516 517 usr.is_admin = user.admin
517 518
518 519 try:
519 520 self.user_perms = set([fill_perms(usr)\
520 521 .permissions['repositories'][repo_name]])
521 522 except:
522 523 self.user_perms = set()
523 524 self.granted_for = ''
524 525 self.username = user.username
525 526 self.repo_name = repo_name
526 527 return self.check_permissions()
527 528
528 529 def check_permissions(self):
529 530 log.debug('checking mercurial protocol '
530 531 'permissions for user:%s repository:%s',
531 532 self.username, self.repo_name)
532 533 if self.required_perms.intersection(self.user_perms):
533 534 log.debug('permission granted')
534 535 return True
535 536 log.debug('permission denied')
536 537 return False
General Comments 0
You need to be logged in to leave comments. Login now