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