##// END OF EJS Templates
auth functions little fix
marcink -
r382:e0ef325c default
parent child Browse files
Show More
@@ -1,427 +1,429
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, Repo2Perm, Repository, Permission
31 31 from sqlalchemy.exc import OperationalError
32 32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 33 import crypt
34 34 from decorator import decorator
35 35 import logging
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 Fills user data with those from database
110 Fills user data with those from database and log out user if not present
111 in database
111 112 @param user:
112 113 """
113 114 sa = meta.Session
114 115 dbuser = sa.query(User).get(user.user_id)
115
116 user.username = dbuser.username
117 user.is_admin = dbuser.admin
118 user.name = dbuser.name
119 user.lastname = dbuser.lastname
120
116 if dbuser:
117 user.username = dbuser.username
118 user.is_admin = dbuser.admin
119 user.name = dbuser.name
120 user.lastname = dbuser.lastname
121 else:
122 user.is_authenticated = False
121 123 meta.Session.remove()
122 124 return user
123 125
124 126 def fill_perms(user):
125 127 """
126 128 Fills user permission attribute with permissions taken from database
127 129 @param user:
128 130 """
129 131
130 132 sa = meta.Session
131 133 user.permissions['repositories'] = {}
132 134 user.permissions['global'] = set()
133 135
134 136 #first fetch default permissions
135 137 default_perms = sa.query(Repo2Perm, Repository, Permission)\
136 138 .join((Repository, Repo2Perm.repository_id == Repository.repo_id))\
137 139 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
138 140 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
139 141 'default').one().user_id).all()
140 142
141 143 if user.is_admin:
142 144 user.permissions['global'].add('hg.admin')
143 145 #admin have all rights set to admin
144 146 for perm in default_perms:
145 147 p = 'repository.admin'
146 148 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
147 149
148 150 else:
149 151 user.permissions['global'].add('repository.create')
150 152 for perm in default_perms:
151 153 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
152 154 #disable defaults for private repos,
153 155 p = 'repository.none'
154 156 elif perm.Repository.user_id == user.user_id:
155 157 #set admin if owner
156 158 p = 'repository.admin'
157 159 else:
158 160 p = perm.Permission.permission_name
159 161
160 162 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
161 163
162 164
163 165 user_perms = sa.query(Repo2Perm, Permission, Repository)\
164 166 .join((Repository, Repo2Perm.repository_id == Repository.repo_id))\
165 167 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
166 168 .filter(Repo2Perm.user_id == user.user_id).all()
167 169 #overwrite userpermissions with defaults
168 170 for perm in user_perms:
169 171 #set write if owner
170 172 if perm.Repository.user_id == user.user_id:
171 173 p = 'repository.write'
172 174 else:
173 175 p = perm.Permission.permission_name
174 176 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
175 177 meta.Session.remove()
176 178 return user
177 179
178 180 def get_user(session):
179 181 """
180 182 Gets user from session, and wraps permissions into user
181 183 @param session:
182 184 """
183 185 user = session.get('hg_app_user', AuthUser())
184 186 if user.is_authenticated:
185 187 user = fill_data(user)
186 188 user = fill_perms(user)
187 189 session['hg_app_user'] = user
188 190 session.save()
189 191 return user
190 192
191 193 #===============================================================================
192 194 # CHECK DECORATORS
193 195 #===============================================================================
194 196 class LoginRequired(object):
195 197 """Must be logged in to execute this function else redirect to login page"""
196 198
197 199 def __call__(self, func):
198 200 return decorator(self.__wrapper, func)
199 201
200 202 def __wrapper(self, func, *fargs, **fkwargs):
201 203 user = session.get('hg_app_user', AuthUser())
202 204 log.debug('Checking login required for user:%s', user.username)
203 205 if user.is_authenticated:
204 206 log.debug('user %s is authenticated', user.username)
205 207 return func(*fargs, **fkwargs)
206 208 else:
207 209 log.warn('user %s not authenticated', user.username)
208 210 log.debug('redirecting to login page')
209 211 return redirect(url('login_home'))
210 212
211 213 class PermsDecorator(object):
212 214 """Base class for decorators"""
213 215
214 216 def __init__(self, *required_perms):
215 217 available_perms = config['available_permissions']
216 218 for perm in required_perms:
217 219 if perm not in available_perms:
218 220 raise Exception("'%s' permission is not defined" % perm)
219 221 self.required_perms = set(required_perms)
220 222 self.user_perms = None
221 223
222 224 def __call__(self, func):
223 225 return decorator(self.__wrapper, func)
224 226
225 227
226 228 def __wrapper(self, func, *fargs, **fkwargs):
227 229 # _wrapper.__name__ = func.__name__
228 230 # _wrapper.__dict__.update(func.__dict__)
229 231 # _wrapper.__doc__ = func.__doc__
230 232
231 233 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
232 234 log.debug('checking %s permissions %s for %s',
233 235 self.__class__.__name__, self.required_perms, func.__name__)
234 236
235 237 if self.check_permissions():
236 238 log.debug('Permission granted for %s', func.__name__)
237 239
238 240 return func(*fargs, **fkwargs)
239 241
240 242 else:
241 243 log.warning('Permission denied for %s', func.__name__)
242 244 #redirect with forbidden ret code
243 245 return abort(403)
244 246
245 247
246 248
247 249 def check_permissions(self):
248 250 """Dummy function for overriding"""
249 251 raise Exception('You have to write this function in child class')
250 252
251 253 class HasPermissionAllDecorator(PermsDecorator):
252 254 """Checks for access permission for all given predicates. All of them
253 255 have to be meet in order to fulfill the request
254 256 """
255 257
256 258 def check_permissions(self):
257 259 if self.required_perms.issubset(self.user_perms.get('global')):
258 260 return True
259 261 return False
260 262
261 263
262 264 class HasPermissionAnyDecorator(PermsDecorator):
263 265 """Checks for access permission for any of given predicates. In order to
264 266 fulfill the request any of predicates must be meet
265 267 """
266 268
267 269 def check_permissions(self):
268 270 if self.required_perms.intersection(self.user_perms.get('global')):
269 271 return True
270 272 return False
271 273
272 274 class HasRepoPermissionAllDecorator(PermsDecorator):
273 275 """Checks for access permission for all given predicates for specific
274 276 repository. All of them have to be meet in order to fulfill the request
275 277 """
276 278
277 279 def check_permissions(self):
278 280 repo_name = get_repo_slug(request)
279 281 try:
280 282 user_perms = set([self.user_perms['repositories'][repo_name]])
281 283 except KeyError:
282 284 return False
283 285 if self.required_perms.issubset(user_perms):
284 286 return True
285 287 return False
286 288
287 289
288 290 class HasRepoPermissionAnyDecorator(PermsDecorator):
289 291 """Checks for access permission for any of given predicates for specific
290 292 repository. In order to fulfill the request any of predicates must be meet
291 293 """
292 294
293 295 def check_permissions(self):
294 296 repo_name = get_repo_slug(request)
295 297
296 298 try:
297 299 user_perms = set([self.user_perms['repositories'][repo_name]])
298 300 except KeyError:
299 301 return False
300 302 if self.required_perms.intersection(user_perms):
301 303 return True
302 304 return False
303 305 #===============================================================================
304 306 # CHECK FUNCTIONS
305 307 #===============================================================================
306 308
307 309 class PermsFunction(object):
308 310 """Base function for other check functions"""
309 311
310 312 def __init__(self, *perms):
311 313 available_perms = config['available_permissions']
312 314
313 315 for perm in perms:
314 316 if perm not in available_perms:
315 317 raise Exception("'%s' permission in not defined" % perm)
316 318 self.required_perms = set(perms)
317 319 self.user_perms = None
318 320 self.granted_for = ''
319 321 self.repo_name = None
320 322
321 323 def __call__(self, check_Location=''):
322 324 user = session.get('hg_app_user', False)
323 325 if not user:
324 326 return False
325 327 self.user_perms = user.permissions
326 328 self.granted_for = user.username
327 329 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
328 330
329 331 if self.check_permissions():
330 332 log.debug('Permission granted for %s @%s', self.granted_for,
331 333 check_Location)
332 334 return True
333 335
334 336 else:
335 337 log.warning('Permission denied for %s @%s', self.granted_for,
336 338 check_Location)
337 339 return False
338 340
339 341 def check_permissions(self):
340 342 """Dummy function for overriding"""
341 343 raise Exception('You have to write this function in child class')
342 344
343 345 class HasPermissionAll(PermsFunction):
344 346 def check_permissions(self):
345 347 if self.required_perms.issubset(self.user_perms.get('global')):
346 348 return True
347 349 return False
348 350
349 351 class HasPermissionAny(PermsFunction):
350 352 def check_permissions(self):
351 353 if self.required_perms.intersection(self.user_perms.get('global')):
352 354 return True
353 355 return False
354 356
355 357 class HasRepoPermissionAll(PermsFunction):
356 358
357 359 def __call__(self, repo_name=None, check_Location=''):
358 360 self.repo_name = repo_name
359 361 return super(HasRepoPermissionAll, self).__call__(check_Location)
360 362
361 363 def check_permissions(self):
362 364 if not self.repo_name:
363 365 self.repo_name = get_repo_slug(request)
364 366
365 367 try:
366 368 self.user_perms = set([self.user_perms['repositories']\
367 369 [self.repo_name]])
368 370 except KeyError:
369 371 return False
370 372 self.granted_for = self.repo_name
371 373 if self.required_perms.issubset(self.user_perms):
372 374 return True
373 375 return False
374 376
375 377 class HasRepoPermissionAny(PermsFunction):
376 378
377 379 def __call__(self, repo_name=None, check_Location=''):
378 380 self.repo_name = repo_name
379 381 return super(HasRepoPermissionAny, self).__call__(check_Location)
380 382
381 383 def check_permissions(self):
382 384 if not self.repo_name:
383 385 self.repo_name = get_repo_slug(request)
384 386
385 387 try:
386 388 self.user_perms = set([self.user_perms['repositories']\
387 389 [self.repo_name]])
388 390 except KeyError:
389 391 return False
390 392 self.granted_for = self.repo_name
391 393 if self.required_perms.intersection(self.user_perms):
392 394 return True
393 395 return False
394 396
395 397 #===============================================================================
396 398 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
397 399 #===============================================================================
398 400
399 401 class HasPermissionAnyMiddleware(object):
400 402 def __init__(self, *perms):
401 403 self.required_perms = set(perms)
402 404
403 405 def __call__(self, user, repo_name):
404 406 usr = AuthUser()
405 407 usr.user_id = user.user_id
406 408 usr.username = user.username
407 409 usr.is_admin = user.admin
408 410
409 411 try:
410 412 self.user_perms = set([fill_perms(usr)\
411 413 .permissions['repositories'][repo_name]])
412 414 except:
413 415 self.user_perms = set()
414 416 self.granted_for = ''
415 417 self.username = user.username
416 418 self.repo_name = repo_name
417 419 return self.check_permissions()
418 420
419 421 def check_permissions(self):
420 422 log.debug('checking mercurial protocol '
421 423 'permissions for user:%s repository:%s',
422 424 self.username, self.repo_name)
423 425 if self.required_perms.intersection(self.user_perms):
424 426 log.debug('permission granted')
425 427 return True
426 428 log.debug('permission denied')
427 429 return False
General Comments 0
You need to be logged in to leave comments. Login now