##// END OF EJS Templates
fixed anonymous access bug.
marcink -
r686:ff6a8196 beta
parent child Browse files
Show More
@@ -1,480 +1,479 b''
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.utils import get_repo_slug
28 28 from rhodecode.model import meta
29 29 from rhodecode.model.user import UserModel
30 30 from rhodecode.model.caching_query import FromCache
31 31 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
32 32 UserToPerm
33 33 import bcrypt
34 34 from decorator import decorator
35 35 import logging
36 36 import random
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40 class PasswordGenerator(object):
41 41 """This is a simple class for generating password from
42 42 different sets of characters
43 43 usage:
44 44 passwd_gen = PasswordGenerator()
45 45 #print 8-letter password containing only big and small letters of alphabet
46 46 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
47 47 """
48 48 ALPHABETS_NUM = r'''1234567890'''#[0]
49 49 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
50 50 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
51 51 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
52 52 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
53 53 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
54 54 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
55 55 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
56 56 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
57 57
58 58 def __init__(self, passwd=''):
59 59 self.passwd = passwd
60 60
61 61 def gen_password(self, len, type):
62 62 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
63 63 return self.passwd
64 64
65 65
66 66 def get_crypt_password(password):
67 67 """Cryptographic function used for password hashing based on sha1
68 68 :param password: password to hash
69 69 """
70 70 return bcrypt.hashpw(password, bcrypt.gensalt(10))
71 71
72 72 def check_password(password, hashed):
73 73 return bcrypt.hashpw(password, hashed) == hashed
74 74
75 75 def authfunc(environ, username, password):
76 76 user = UserModel().get_by_username(username, cache=False)
77 77
78 78 if user:
79 79 if user.active:
80 80
81 81 if user.username == 'default' and user.active:
82 82 log.info('user %s authenticated correctly', username)
83 83 return True
84 84
85 85 elif user.username == username and check_password(password, user.password):
86 86 log.info('user %s authenticated correctly', username)
87 87 return True
88 88 else:
89 89 log.error('user %s is disabled', username)
90 90
91 91 return False
92 92
93 93 class AuthUser(object):
94 94 """
95 95 A simple object that handles a mercurial username for authentication
96 96 """
97 97 def __init__(self):
98 98 self.username = 'None'
99 99 self.name = ''
100 100 self.lastname = ''
101 101 self.email = ''
102 102 self.user_id = None
103 103 self.is_authenticated = False
104 104 self.is_admin = False
105 105 self.permissions = {}
106 106
107 107 def __repr__(self):
108 108 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
109 109
110 110 def set_available_permissions(config):
111 111 """
112 112 This function will propagate pylons globals with all available defined
113 113 permission given in db. We don't wannt to check each time from db for new
114 114 permissions since adding a new permission also requires application restart
115 115 ie. to decorate new views with the newly created permission
116 116 :param config:
117 117 """
118 118 log.info('getting information about all available permissions')
119 119 try:
120 120 sa = meta.Session()
121 121 all_perms = sa.query(Permission).all()
122 122 except:
123 123 pass
124 124 finally:
125 125 meta.Session.remove()
126 126
127 127 config['available_permissions'] = [x.permission_name for x in all_perms]
128 128
129 129 def set_base_path(config):
130 130 config['base_path'] = config['pylons.app_globals'].base_path
131 131
132 132
133 133 def fill_perms(user):
134 134 """
135 135 Fills user permission attribute with permissions taken from database
136 136 :param user:
137 137 """
138 138
139 139 sa = meta.Session()
140 140 user.permissions['repositories'] = {}
141 141 user.permissions['global'] = set()
142 142
143 143 #===========================================================================
144 144 # fetch default permissions
145 145 #===========================================================================
146 146 default_user = UserModel(sa).get_by_username('default', cache=True)
147 147
148 148 default_perms = sa.query(RepoToPerm, Repository, Permission)\
149 149 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
150 150 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
151 151 .filter(RepoToPerm.user == default_user).all()
152 152
153 153 if user.is_admin:
154 154 #=======================================================================
155 155 # #admin have all default rights set to admin
156 156 #=======================================================================
157 157 user.permissions['global'].add('hg.admin')
158 158
159 159 for perm in default_perms:
160 160 p = 'repository.admin'
161 161 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
162 162
163 163 else:
164 164 #=======================================================================
165 165 # set default permissions
166 166 #=======================================================================
167 167
168 168 #default global
169 169 default_global_perms = sa.query(UserToPerm)\
170 170 .filter(UserToPerm.user == sa.query(User).filter(User.username ==
171 171 'default').one())
172 172
173 173 for perm in default_global_perms:
174 174 user.permissions['global'].add(perm.permission.permission_name)
175 175
176 176 #default repositories
177 177 for perm in default_perms:
178 178 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
179 179 #disable defaults for private repos,
180 180 p = 'repository.none'
181 181 elif perm.Repository.user_id == user.user_id:
182 182 #set admin if owner
183 183 p = 'repository.admin'
184 184 else:
185 185 p = perm.Permission.permission_name
186 186
187 187 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
188 188
189 189 #=======================================================================
190 190 # #overwrite default with user permissions if any
191 191 #=======================================================================
192 192 user_perms = sa.query(RepoToPerm, Permission, Repository)\
193 193 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
194 194 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
195 195 .filter(RepoToPerm.user_id == user.user_id).all()
196 196
197 197 for perm in user_perms:
198 198 if perm.Repository.user_id == user.user_id:#set admin if owner
199 199 p = 'repository.admin'
200 200 else:
201 201 p = perm.Permission.permission_name
202 202 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
203 203 meta.Session.remove()
204 204 return user
205 205
206 206 def get_user(session):
207 207 """
208 208 Gets user from session, and wraps permissions into user
209 209 :param session:
210 210 """
211 211 user = session.get('rhodecode_user', AuthUser())
212
213
214 212 #if the user is not logged in we check for anonymous access
215 213 #if user is logged and it's a default user check if we still have anonymous
216 214 #access enabled
217 215 if user.user_id is None or user.username == 'default':
218 216 anonymous_user = UserModel().get_by_username('default', cache=True)
219 217 if anonymous_user.active is True:
220 218 #then we set this user is logged in
221 219 user.is_authenticated = True
220 user.user_id = anonymous_user.user_id
222 221 else:
223 222 user.is_authenticated = False
224 223
225 224 if user.is_authenticated:
226 225 user = UserModel().fill_data(user)
227 226
228 227 user = fill_perms(user)
229 228 session['rhodecode_user'] = user
230 229 session.save()
231 230 return user
232 231
233 232 #===============================================================================
234 233 # CHECK DECORATORS
235 234 #===============================================================================
236 235 class LoginRequired(object):
237 236 """Must be logged in to execute this function else redirect to login page"""
238 237
239 238 def __call__(self, func):
240 239 return decorator(self.__wrapper, func)
241 240
242 241 def __wrapper(self, func, *fargs, **fkwargs):
243 242 user = session.get('rhodecode_user', AuthUser())
244 243 log.debug('Checking login required for user:%s', user.username)
245 244 if user.is_authenticated:
246 245 log.debug('user %s is authenticated', user.username)
247 246 return func(*fargs, **fkwargs)
248 247 else:
249 248 log.warn('user %s not authenticated', user.username)
250 249
251 250 p = ''
252 251 if request.environ.get('SCRIPT_NAME') != '/':
253 252 p += request.environ.get('SCRIPT_NAME')
254 253
255 254 p += request.environ.get('PATH_INFO')
256 255 if request.environ.get('QUERY_STRING'):
257 256 p += '?' + request.environ.get('QUERY_STRING')
258 257
259 258 log.debug('redirecting to login page with %s', p)
260 259 return redirect(url('login_home', came_from=p))
261 260
262 261 class PermsDecorator(object):
263 262 """Base class for decorators"""
264 263
265 264 def __init__(self, *required_perms):
266 265 available_perms = config['available_permissions']
267 266 for perm in required_perms:
268 267 if perm not in available_perms:
269 268 raise Exception("'%s' permission is not defined" % perm)
270 269 self.required_perms = set(required_perms)
271 270 self.user_perms = None
272 271
273 272 def __call__(self, func):
274 273 return decorator(self.__wrapper, func)
275 274
276 275
277 276 def __wrapper(self, func, *fargs, **fkwargs):
278 277 # _wrapper.__name__ = func.__name__
279 278 # _wrapper.__dict__.update(func.__dict__)
280 279 # _wrapper.__doc__ = func.__doc__
281 280 self.user = session.get('rhodecode_user', AuthUser())
282 281 self.user_perms = self.user.permissions
283 282 log.debug('checking %s permissions %s for %s %s',
284 283 self.__class__.__name__, self.required_perms, func.__name__,
285 284 self.user)
286 285
287 286 if self.check_permissions():
288 287 log.debug('Permission granted for %s %s', func.__name__, self.user)
289 288
290 289 return func(*fargs, **fkwargs)
291 290
292 291 else:
293 292 log.warning('Permission denied for %s %s', func.__name__, self.user)
294 293 #redirect with forbidden ret code
295 294 return abort(403)
296 295
297 296
298 297
299 298 def check_permissions(self):
300 299 """Dummy function for overriding"""
301 300 raise Exception('You have to write this function in child class')
302 301
303 302 class HasPermissionAllDecorator(PermsDecorator):
304 303 """Checks for access permission for all given predicates. All of them
305 304 have to be meet in order to fulfill the request
306 305 """
307 306
308 307 def check_permissions(self):
309 308 if self.required_perms.issubset(self.user_perms.get('global')):
310 309 return True
311 310 return False
312 311
313 312
314 313 class HasPermissionAnyDecorator(PermsDecorator):
315 314 """Checks for access permission for any of given predicates. In order to
316 315 fulfill the request any of predicates must be meet
317 316 """
318 317
319 318 def check_permissions(self):
320 319 if self.required_perms.intersection(self.user_perms.get('global')):
321 320 return True
322 321 return False
323 322
324 323 class HasRepoPermissionAllDecorator(PermsDecorator):
325 324 """Checks for access permission for all given predicates for specific
326 325 repository. All of them have to be meet in order to fulfill the request
327 326 """
328 327
329 328 def check_permissions(self):
330 329 repo_name = get_repo_slug(request)
331 330 try:
332 331 user_perms = set([self.user_perms['repositories'][repo_name]])
333 332 except KeyError:
334 333 return False
335 334 if self.required_perms.issubset(user_perms):
336 335 return True
337 336 return False
338 337
339 338
340 339 class HasRepoPermissionAnyDecorator(PermsDecorator):
341 340 """Checks for access permission for any of given predicates for specific
342 341 repository. In order to fulfill the request any of predicates must be meet
343 342 """
344 343
345 344 def check_permissions(self):
346 345 repo_name = get_repo_slug(request)
347 346
348 347 try:
349 348 user_perms = set([self.user_perms['repositories'][repo_name]])
350 349 except KeyError:
351 350 return False
352 351 if self.required_perms.intersection(user_perms):
353 352 return True
354 353 return False
355 354 #===============================================================================
356 355 # CHECK FUNCTIONS
357 356 #===============================================================================
358 357
359 358 class PermsFunction(object):
360 359 """Base function for other check functions"""
361 360
362 361 def __init__(self, *perms):
363 362 available_perms = config['available_permissions']
364 363
365 364 for perm in perms:
366 365 if perm not in available_perms:
367 366 raise Exception("'%s' permission in not defined" % perm)
368 367 self.required_perms = set(perms)
369 368 self.user_perms = None
370 369 self.granted_for = ''
371 370 self.repo_name = None
372 371
373 372 def __call__(self, check_Location=''):
374 373 user = session.get('rhodecode_user', False)
375 374 if not user:
376 375 return False
377 376 self.user_perms = user.permissions
378 377 self.granted_for = user.username
379 378 log.debug('checking %s %s %s', self.__class__.__name__,
380 379 self.required_perms, user)
381 380
382 381 if self.check_permissions():
383 382 log.debug('Permission granted for %s @ %s %s', self.granted_for,
384 383 check_Location, user)
385 384 return True
386 385
387 386 else:
388 387 log.warning('Permission denied for %s @ %s %s', self.granted_for,
389 388 check_Location, user)
390 389 return False
391 390
392 391 def check_permissions(self):
393 392 """Dummy function for overriding"""
394 393 raise Exception('You have to write this function in child class')
395 394
396 395 class HasPermissionAll(PermsFunction):
397 396 def check_permissions(self):
398 397 if self.required_perms.issubset(self.user_perms.get('global')):
399 398 return True
400 399 return False
401 400
402 401 class HasPermissionAny(PermsFunction):
403 402 def check_permissions(self):
404 403 if self.required_perms.intersection(self.user_perms.get('global')):
405 404 return True
406 405 return False
407 406
408 407 class HasRepoPermissionAll(PermsFunction):
409 408
410 409 def __call__(self, repo_name=None, check_Location=''):
411 410 self.repo_name = repo_name
412 411 return super(HasRepoPermissionAll, self).__call__(check_Location)
413 412
414 413 def check_permissions(self):
415 414 if not self.repo_name:
416 415 self.repo_name = get_repo_slug(request)
417 416
418 417 try:
419 418 self.user_perms = set([self.user_perms['repositories']\
420 419 [self.repo_name]])
421 420 except KeyError:
422 421 return False
423 422 self.granted_for = self.repo_name
424 423 if self.required_perms.issubset(self.user_perms):
425 424 return True
426 425 return False
427 426
428 427 class HasRepoPermissionAny(PermsFunction):
429 428
430 429 def __call__(self, repo_name=None, check_Location=''):
431 430 self.repo_name = repo_name
432 431 return super(HasRepoPermissionAny, self).__call__(check_Location)
433 432
434 433 def check_permissions(self):
435 434 if not self.repo_name:
436 435 self.repo_name = get_repo_slug(request)
437 436
438 437 try:
439 438 self.user_perms = set([self.user_perms['repositories']\
440 439 [self.repo_name]])
441 440 except KeyError:
442 441 return False
443 442 self.granted_for = self.repo_name
444 443 if self.required_perms.intersection(self.user_perms):
445 444 return True
446 445 return False
447 446
448 447 #===============================================================================
449 448 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
450 449 #===============================================================================
451 450
452 451 class HasPermissionAnyMiddleware(object):
453 452 def __init__(self, *perms):
454 453 self.required_perms = set(perms)
455 454
456 455 def __call__(self, user, repo_name):
457 456 usr = AuthUser()
458 457 usr.user_id = user.user_id
459 458 usr.username = user.username
460 459 usr.is_admin = user.admin
461 460
462 461 try:
463 462 self.user_perms = set([fill_perms(usr)\
464 463 .permissions['repositories'][repo_name]])
465 464 except:
466 465 self.user_perms = set()
467 466 self.granted_for = ''
468 467 self.username = user.username
469 468 self.repo_name = repo_name
470 469 return self.check_permissions()
471 470
472 471 def check_permissions(self):
473 472 log.debug('checking mercurial protocol '
474 473 'permissions for user:%s repository:%s',
475 474 self.username, self.repo_name)
476 475 if self.required_perms.intersection(self.user_perms):
477 476 log.debug('permission granted')
478 477 return True
479 478 log.debug('permission denied')
480 479 return False
@@ -1,166 +1,171 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Model for users
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 9, 2010
22 22 Model for users
23 23 :author: marcink
24 24 """
25 25
26 26 from pylons.i18n.translation import _
27 27 from rhodecode.model.caching_query import FromCache
28 28 from rhodecode.model.db import User
29 29 from rhodecode.model.meta import Session
30 30 import logging
31 31 import traceback
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35 class DefaultUserException(Exception):pass
36 36
37 37 class UserModel(object):
38 38
39 39 def __init__(self, sa=None):
40 40 if not sa:
41 41 self.sa = Session()
42 42 else:
43 43 self.sa = sa
44 44
45 45 def get(self, user_id, cache=False):
46 46 user = self.sa.query(User)
47 47 if cache:
48 48 user = user.options(FromCache("sql_cache_short",
49 49 "get_user_%s" % user_id))
50 50 return user.get(user_id)
51 51
52 52
53 53 def get_by_username(self, username, cache=False):
54 54 user = self.sa.query(User)\
55 55 .filter(User.username == username)
56 56 if cache:
57 57 user = user.options(FromCache("sql_cache_short",
58 58 "get_user_%s" % username))
59 59 return user.scalar()
60 60
61 61 def create(self, form_data):
62 62 try:
63 63 new_user = User()
64 64 for k, v in form_data.items():
65 65 setattr(new_user, k, v)
66 66
67 67 self.sa.add(new_user)
68 68 self.sa.commit()
69 69 except:
70 70 log.error(traceback.format_exc())
71 71 self.sa.rollback()
72 72 raise
73 73
74 74 def create_registration(self, form_data):
75 75 try:
76 76 new_user = User()
77 77 for k, v in form_data.items():
78 78 if k != 'admin':
79 79 setattr(new_user, k, v)
80 80
81 81 self.sa.add(new_user)
82 82 self.sa.commit()
83 83 except:
84 84 log.error(traceback.format_exc())
85 85 self.sa.rollback()
86 86 raise
87 87
88 88 def update(self, user_id, form_data):
89 89 try:
90 90 new_user = self.get(user_id, cache=False)
91 91 if new_user.username == 'default':
92 92 raise DefaultUserException(
93 93 _("You can't Edit this user since it's"
94 94 " crucial for entire application"))
95 95 for k, v in form_data.items():
96 96 if k == 'new_password' and v != '':
97 97 new_user.password = v
98 98 else:
99 99 setattr(new_user, k, v)
100 100
101 101 self.sa.add(new_user)
102 102 self.sa.commit()
103 103 except:
104 104 log.error(traceback.format_exc())
105 105 self.sa.rollback()
106 106 raise
107 107
108 108 def update_my_account(self, user_id, form_data):
109 109 try:
110 110 new_user = self.get(user_id, cache=False)
111 111 if new_user.username == 'default':
112 112 raise DefaultUserException(
113 113 _("You can't Edit this user since it's"
114 114 " crucial for entire application"))
115 115 for k, v in form_data.items():
116 116 if k == 'new_password' and v != '':
117 117 new_user.password = v
118 118 else:
119 119 if k not in ['admin', 'active']:
120 120 setattr(new_user, k, v)
121 121
122 122 self.sa.add(new_user)
123 123 self.sa.commit()
124 124 except:
125 125 log.error(traceback.format_exc())
126 126 self.sa.rollback()
127 127 raise
128 128
129 129 def delete(self, user_id):
130 130 try:
131 131 user = self.get(user_id, cache=False)
132 132 if user.username == 'default':
133 133 raise DefaultUserException(
134 134 _("You can't remove this user since it's"
135 135 " crucial for entire application"))
136 136 self.sa.delete(user)
137 137 self.sa.commit()
138 138 except:
139 139 log.error(traceback.format_exc())
140 140 self.sa.rollback()
141 141 raise
142 142
143 143 def reset_password(self, data):
144 144 from rhodecode.lib.celerylib import tasks, run_task
145 145 run_task(tasks.reset_user_password, data['email'])
146 146
147 147
148 148 def fill_data(self, user):
149 149 """
150 150 Fills user data with those from database and log out user if not
151 151 present in database
152 152 :param user:
153 153 """
154
155 if not hasattr(user, 'user_id') or user.user_id is None:
156 raise Exception('passed in user has to have the user_id attribute')
157
158
154 159 log.debug('filling auth user data')
155 160 try:
156 161 dbuser = self.get(user.user_id)
157 162 user.username = dbuser.username
158 163 user.is_admin = dbuser.admin
159 164 user.name = dbuser.name
160 165 user.lastname = dbuser.lastname
161 166 user.email = dbuser.email
162 167 except:
163 168 log.error(traceback.format_exc())
164 169 user.is_authenticated = False
165 170
166 171 return user
General Comments 0
You need to be logged in to leave comments. Login now