##// END OF EJS Templates
fixes #30. Rewrite default permissions query + some other small fixes
marcink -
r423:16253f33 default
parent child Browse files
Show More
@@ -1,450 +1,452 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 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 UserToPerm
32 32 from sqlalchemy.exc import OperationalError
33 33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 34 import bcrypt
35 35 from decorator import decorator
36 36 import logging
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40 def get_crypt_password(password):
41 41 """Cryptographic function used for password hashing based on sha1
42 42 @param password: password to hash
43 43 """
44 44 return bcrypt.hashpw(password, bcrypt.gensalt(10))
45 45
46 46 def check_password(password, hashed):
47 47 return bcrypt.hashpw(password, hashed) == hashed
48 48
49 49 @cache_region('super_short_term', 'cached_user')
50 50 def get_user_cached(username):
51 51 sa = meta.Session
52 52 try:
53 53 user = sa.query(User).filter(User.username == username).one()
54 54 finally:
55 55 meta.Session.remove()
56 56 return user
57 57
58 58 def authfunc(environ, username, password):
59 59 try:
60 60 user = get_user_cached(username)
61 61 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
62 62 log.error(e)
63 63 user = None
64 64
65 65 if user:
66 66 if user.active:
67 67 if user.username == username and check_password(password, user.password):
68 68 log.info('user %s authenticated correctly', username)
69 69 return True
70 70 else:
71 71 log.error('user %s is disabled', username)
72 72
73 73 return False
74 74
75 75 class AuthUser(object):
76 76 """
77 77 A simple object that handles a mercurial username for authentication
78 78 """
79 79 def __init__(self):
80 80 self.username = 'None'
81 81 self.name = ''
82 82 self.lastname = ''
83 83 self.email = ''
84 84 self.user_id = None
85 85 self.is_authenticated = False
86 86 self.is_admin = False
87 87 self.permissions = {}
88 88
89 89
90 90 def set_available_permissions(config):
91 91 """
92 92 This function will propagate pylons globals with all available defined
93 93 permission given in db. We don't wannt to check each time from db for new
94 94 permissions since adding a new permission also requires application restart
95 95 ie. to decorate new views with the newly created permission
96 96 @param config:
97 97 """
98 98 log.info('getting information about all available permissions')
99 99 try:
100 100 sa = meta.Session
101 101 all_perms = sa.query(Permission).all()
102 102 finally:
103 103 meta.Session.remove()
104 104
105 105 config['available_permissions'] = [x.permission_name for x in all_perms]
106 106
107 107 def set_base_path(config):
108 108 config['base_path'] = config['pylons.app_globals'].base_path
109 109
110 110 def fill_data(user):
111 111 """
112 112 Fills user data with those from database and log out user if not present
113 113 in database
114 114 @param user:
115 115 """
116 116 sa = meta.Session
117 117 dbuser = sa.query(User).get(user.user_id)
118 118 if dbuser:
119 119 user.username = dbuser.username
120 120 user.is_admin = dbuser.admin
121 121 user.name = dbuser.name
122 122 user.lastname = dbuser.lastname
123 123 user.email = dbuser.email
124 124 else:
125 125 user.is_authenticated = False
126 126 meta.Session.remove()
127 127 from pprint import pprint
128 128 pprint(user.permissions)
129 129 return user
130 130
131 131 def fill_perms(user):
132 132 """
133 133 Fills user permission attribute with permissions taken from database
134 134 @param user:
135 135 """
136 136
137 137 sa = meta.Session
138 138 user.permissions['repositories'] = {}
139 139 user.permissions['global'] = set()
140 140
141 141 #===========================================================================
142 142 # fetch default permissions
143 143 #===========================================================================
144 default_perms = sa.query(RepoToPerm, UserToPerm, Repository, Permission)\
145 .outerjoin((UserToPerm, RepoToPerm.user_id == UserToPerm.user_id))\
144 default_perms = sa.query(RepoToPerm, Repository, Permission)\
146 145 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
147 146 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
148 .filter(RepoToPerm.user_id == sa.query(User).filter(User.username ==
149 'default').one().user_id).all()
147 .filter(RepoToPerm.user == sa.query(User).filter(User.username ==
148 'default').scalar()).all()
150 149
151 150 if user.is_admin:
152 151 #=======================================================================
153 # #admin have all rights set to admin
152 # #admin have all default rights set to admin
154 153 #=======================================================================
155 154 user.permissions['global'].add('hg.admin')
156 155
157 156 for perm in default_perms:
158 157 p = 'repository.admin'
159 158 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
160 159
161 160 else:
162 161 #=======================================================================
163 162 # set default permissions
164 163 #=======================================================================
165 164
166 165 #default global
167 for perm in default_perms:
168 user.permissions['global'].add(perm.UserToPerm.permission.permission_name)
166 default_global_perms = sa.query(UserToPerm)\
167 .filter(UserToPerm.user == sa.query(User).filter(User.username ==
168 'default').one())
169
170 for perm in default_global_perms:
171 user.permissions['global'].add(perm.permission.permission_name)
169 172
170 173 #default repositories
171 174 for perm in default_perms:
172 175 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
173 176 #disable defaults for private repos,
174 177 p = 'repository.none'
175 178 elif perm.Repository.user_id == user.user_id:
176 179 #set admin if owner
177 180 p = 'repository.admin'
178 181 else:
179 182 p = perm.Permission.permission_name
180 183
181 184 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
182 185
183 186 #=======================================================================
184 187 # #overwrite default with user permissions if any
185 188 #=======================================================================
186 user_perms = sa.query(RepoToPerm, UserToPerm, Permission, Repository)\
187 .outerjoin((UserToPerm, RepoToPerm.user_id == UserToPerm.user_id))\
189 user_perms = sa.query(RepoToPerm, Permission, Repository)\
188 190 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
189 191 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
190 192 .filter(RepoToPerm.user_id == user.user_id).all()
191 193
192 194 for perm in user_perms:
193 195 if perm.Repository.user_id == user.user_id:#set admin if owner
194 196 p = 'repository.admin'
195 197 else:
196 198 p = perm.Permission.permission_name
197 199 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
198 200 meta.Session.remove()
199 201 return user
200 202
201 203 def get_user(session):
202 204 """
203 205 Gets user from session, and wraps permissions into user
204 206 @param session:
205 207 """
206 208 user = session.get('hg_app_user', AuthUser())
207 209 if user.is_authenticated:
208 210 user = fill_data(user)
209 211 user = fill_perms(user)
210 212 session['hg_app_user'] = user
211 213 session.save()
212 214 return user
213 215
214 216 #===============================================================================
215 217 # CHECK DECORATORS
216 218 #===============================================================================
217 219 class LoginRequired(object):
218 220 """Must be logged in to execute this function else redirect to login page"""
219 221
220 222 def __call__(self, func):
221 223 return decorator(self.__wrapper, func)
222 224
223 225 def __wrapper(self, func, *fargs, **fkwargs):
224 226 user = session.get('hg_app_user', AuthUser())
225 227 log.debug('Checking login required for user:%s', user.username)
226 228 if user.is_authenticated:
227 229 log.debug('user %s is authenticated', user.username)
228 230 return func(*fargs, **fkwargs)
229 231 else:
230 232 log.warn('user %s not authenticated', user.username)
231 233 log.debug('redirecting to login page')
232 234 return redirect(url('login_home'))
233 235
234 236 class PermsDecorator(object):
235 237 """Base class for decorators"""
236 238
237 239 def __init__(self, *required_perms):
238 240 available_perms = config['available_permissions']
239 241 for perm in required_perms:
240 242 if perm not in available_perms:
241 243 raise Exception("'%s' permission is not defined" % perm)
242 244 self.required_perms = set(required_perms)
243 245 self.user_perms = None
244 246
245 247 def __call__(self, func):
246 248 return decorator(self.__wrapper, func)
247 249
248 250
249 251 def __wrapper(self, func, *fargs, **fkwargs):
250 252 # _wrapper.__name__ = func.__name__
251 253 # _wrapper.__dict__.update(func.__dict__)
252 254 # _wrapper.__doc__ = func.__doc__
253 255
254 256 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
255 257 log.debug('checking %s permissions %s for %s',
256 258 self.__class__.__name__, self.required_perms, func.__name__)
257 259
258 260 if self.check_permissions():
259 261 log.debug('Permission granted for %s', func.__name__)
260 262
261 263 return func(*fargs, **fkwargs)
262 264
263 265 else:
264 266 log.warning('Permission denied for %s', func.__name__)
265 267 #redirect with forbidden ret code
266 268 return abort(403)
267 269
268 270
269 271
270 272 def check_permissions(self):
271 273 """Dummy function for overriding"""
272 274 raise Exception('You have to write this function in child class')
273 275
274 276 class HasPermissionAllDecorator(PermsDecorator):
275 277 """Checks for access permission for all given predicates. All of them
276 278 have to be meet in order to fulfill the request
277 279 """
278 280
279 281 def check_permissions(self):
280 282 if self.required_perms.issubset(self.user_perms.get('global')):
281 283 return True
282 284 return False
283 285
284 286
285 287 class HasPermissionAnyDecorator(PermsDecorator):
286 288 """Checks for access permission for any of given predicates. In order to
287 289 fulfill the request any of predicates must be meet
288 290 """
289 291
290 292 def check_permissions(self):
291 293 if self.required_perms.intersection(self.user_perms.get('global')):
292 294 return True
293 295 return False
294 296
295 297 class HasRepoPermissionAllDecorator(PermsDecorator):
296 298 """Checks for access permission for all given predicates for specific
297 299 repository. All of them have to be meet in order to fulfill the request
298 300 """
299 301
300 302 def check_permissions(self):
301 303 repo_name = get_repo_slug(request)
302 304 try:
303 305 user_perms = set([self.user_perms['repositories'][repo_name]])
304 306 except KeyError:
305 307 return False
306 308 if self.required_perms.issubset(user_perms):
307 309 return True
308 310 return False
309 311
310 312
311 313 class HasRepoPermissionAnyDecorator(PermsDecorator):
312 314 """Checks for access permission for any of given predicates for specific
313 315 repository. In order to fulfill the request any of predicates must be meet
314 316 """
315 317
316 318 def check_permissions(self):
317 319 repo_name = get_repo_slug(request)
318 320
319 321 try:
320 322 user_perms = set([self.user_perms['repositories'][repo_name]])
321 323 except KeyError:
322 324 return False
323 325 if self.required_perms.intersection(user_perms):
324 326 return True
325 327 return False
326 328 #===============================================================================
327 329 # CHECK FUNCTIONS
328 330 #===============================================================================
329 331
330 332 class PermsFunction(object):
331 333 """Base function for other check functions"""
332 334
333 335 def __init__(self, *perms):
334 336 available_perms = config['available_permissions']
335 337
336 338 for perm in perms:
337 339 if perm not in available_perms:
338 340 raise Exception("'%s' permission in not defined" % perm)
339 341 self.required_perms = set(perms)
340 342 self.user_perms = None
341 343 self.granted_for = ''
342 344 self.repo_name = None
343 345
344 346 def __call__(self, check_Location=''):
345 347 user = session.get('hg_app_user', False)
346 348 if not user:
347 349 return False
348 350 self.user_perms = user.permissions
349 351 self.granted_for = user.username
350 352 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
351 353
352 354 if self.check_permissions():
353 355 log.debug('Permission granted for %s @%s', self.granted_for,
354 356 check_Location)
355 357 return True
356 358
357 359 else:
358 360 log.warning('Permission denied for %s @%s', self.granted_for,
359 361 check_Location)
360 362 return False
361 363
362 364 def check_permissions(self):
363 365 """Dummy function for overriding"""
364 366 raise Exception('You have to write this function in child class')
365 367
366 368 class HasPermissionAll(PermsFunction):
367 369 def check_permissions(self):
368 370 if self.required_perms.issubset(self.user_perms.get('global')):
369 371 return True
370 372 return False
371 373
372 374 class HasPermissionAny(PermsFunction):
373 375 def check_permissions(self):
374 376 if self.required_perms.intersection(self.user_perms.get('global')):
375 377 return True
376 378 return False
377 379
378 380 class HasRepoPermissionAll(PermsFunction):
379 381
380 382 def __call__(self, repo_name=None, check_Location=''):
381 383 self.repo_name = repo_name
382 384 return super(HasRepoPermissionAll, self).__call__(check_Location)
383 385
384 386 def check_permissions(self):
385 387 if not self.repo_name:
386 388 self.repo_name = get_repo_slug(request)
387 389
388 390 try:
389 391 self.user_perms = set([self.user_perms['repositories']\
390 392 [self.repo_name]])
391 393 except KeyError:
392 394 return False
393 395 self.granted_for = self.repo_name
394 396 if self.required_perms.issubset(self.user_perms):
395 397 return True
396 398 return False
397 399
398 400 class HasRepoPermissionAny(PermsFunction):
399 401
400 402 def __call__(self, repo_name=None, check_Location=''):
401 403 self.repo_name = repo_name
402 404 return super(HasRepoPermissionAny, self).__call__(check_Location)
403 405
404 406 def check_permissions(self):
405 407 if not self.repo_name:
406 408 self.repo_name = get_repo_slug(request)
407 409
408 410 try:
409 411 self.user_perms = set([self.user_perms['repositories']\
410 412 [self.repo_name]])
411 413 except KeyError:
412 414 return False
413 415 self.granted_for = self.repo_name
414 416 if self.required_perms.intersection(self.user_perms):
415 417 return True
416 418 return False
417 419
418 420 #===============================================================================
419 421 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
420 422 #===============================================================================
421 423
422 424 class HasPermissionAnyMiddleware(object):
423 425 def __init__(self, *perms):
424 426 self.required_perms = set(perms)
425 427
426 428 def __call__(self, user, repo_name):
427 429 usr = AuthUser()
428 430 usr.user_id = user.user_id
429 431 usr.username = user.username
430 432 usr.is_admin = user.admin
431 433
432 434 try:
433 435 self.user_perms = set([fill_perms(usr)\
434 436 .permissions['repositories'][repo_name]])
435 437 except:
436 438 self.user_perms = set()
437 439 self.granted_for = ''
438 440 self.username = user.username
439 441 self.repo_name = repo_name
440 442 return self.check_permissions()
441 443
442 444 def check_permissions(self):
443 445 log.debug('checking mercurial protocol '
444 446 'permissions for user:%s repository:%s',
445 447 self.username, self.repo_name)
446 448 if self.required_perms.intersection(self.user_perms):
447 449 log.debug('permission granted')
448 450 return True
449 451 log.debug('permission denied')
450 452 return False
General Comments 0
You need to be logged in to leave comments. Login now