comparison pylons_app/lib/auth.py @ 329:d6e2817734d2

Full rewrite of auth module, new functions/decorators. FIxed auth user
author Marcin Kuzminski <marcin@python-works.com>
date Tue, 29 Jun 2010 20:41:54 +0200
parents d303aacb3349
children f5f290d68646
comparison
equal deleted inserted replaced
328:b00945765e7b 329:d6e2817734d2
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # encoding: utf-8 2 # encoding: utf-8
3 # authentication and permission libraries 3 # authentication and permission libraries
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 # This program is free software; you can redistribute it and/or 6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License 7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2 8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license. 9 # of the License or (at your opinion) any later version of the license.
10 # 10 #
22 22
23 @author: marcink 23 @author: marcink
24 """ 24 """
25 25
26 from functools import wraps 26 from functools import wraps
27 from pylons import session, url, app_globals as g 27 from pylons import session, url, request
28 from pylons.controllers.util import abort, redirect 28 from pylons.controllers.util import abort, redirect
29 from pylons_app.model import meta 29 from pylons_app.model import meta
30 from pylons_app.model.db import User, Repo2Perm 30 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
31 from pylons_app.lib.utils import get_repo_slug
31 from sqlalchemy.exc import OperationalError 32 from sqlalchemy.exc import OperationalError
32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound 33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 import crypt 34 import crypt
34 import logging 35 import logging
35 log = logging.getLogger(__name__) 36 from pylons import config
37 log = logging.getLogger(__name__)
36 38
37 def get_crypt_password(password): 39 def get_crypt_password(password):
38 """ 40 """
39 Cryptographic function used for password hashing 41 Cryptographic function used for password hashing
40 @param password: password to hash 42 @param password: password to hash
62 64
63 class AuthUser(object): 65 class AuthUser(object):
64 """ 66 """
65 A simple object that handles a mercurial username for authentication 67 A simple object that handles a mercurial username for authentication
66 """ 68 """
67 username = 'None'
68 user_id = None
69 is_authenticated = False
70 is_admin = False
71 permissions = set()
72 group = set()
73
74 def __init__(self): 69 def __init__(self):
75 pass 70 self.username = 'None'
76 71 self.user_id = None
72 self.is_authenticated = False
73 self.is_admin = False
74 self.permissions = {}
77 75
78 76
79 def set_available_permissions(config): 77 def set_available_permissions(config):
80 """ 78 """
81 This function will propagate pylons globals with all available defined 79 This function will propagate pylons globals with all available defined
82 permission given in db. We don't wannt to check each time from db for new 80 permission given in db. We don't wannt to check each time from db for new
83 permissions since adding a new permission also requires application restart 81 permissions since adding a new permission also requires application restart
84 ie. to decorate new views with the newly created permission 82 ie. to decorate new views with the newly created permission
85 @param config: 83 @param config:
86 """ 84 """
87 from pylons_app.model.meta import Session 85 log.info('getting information about all available permissions')
88 from pylons_app.model.db import Permission 86 sa = meta.Session
89 logging.info('getting information about all available permissions')
90 sa = Session()
91 all_perms = sa.query(Permission).all() 87 all_perms = sa.query(Permission).all()
92 config['pylons.app_globals'].available_permissions = [x.permission_name for x in all_perms] 88 config['available_permissions'] = [x.permission_name for x in all_perms]
93 89
90 def set_base_path(config):
91 config['base_path'] = config['pylons.app_globals'].base_path
92
93 def fill_perms(user):
94 sa = meta.Session
95 user.permissions['repositories'] = {}
96
97 #first fetch default permissions
98 default_perms = sa.query(Repo2Perm, Repository, Permission)\
99 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
100 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
101 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
102 'default').one().user_id).all()
103
104 if user.is_admin:
105 user.permissions['global'] = set(['hg.admin'])
106 #admin have all rights full
107 for perm in default_perms:
108 p = 'repository.admin'
109 user.permissions['repositories'][perm.Repo2Perm.repository] = p
110
111 else:
112 user.permissions['global'] = set()
113 for perm in default_perms:
114 if perm.Repository.private:
115 #disable defaults for private repos,
116 p = 'repository.none'
117 elif perm.Repository.user_id == user.user_id:
118 #set admin if owner
119 p = 'repository.admin'
120 else:
121 p = perm.Permission.permission_name
122
123 user.permissions['repositories'][perm.Repo2Perm.repository] = p
124
125
126 user_perms = sa.query(Repo2Perm, Permission, Repository)\
127 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
128 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
129 .filter(Repo2Perm.user_id == user.user_id).all()
130 #overwrite userpermissions with defaults
131 for perm in user_perms:
132 #set write if owner
133 if perm.Repository.user_id == user.user_id:
134 p = 'repository.write'
135 else:
136 p = perm.Permission.permission_name
137 user.permissions['repositories'][perm.Repo2Perm.repository] = p
138 return user
139
94 def get_user(session): 140 def get_user(session):
95 """ 141 """
96 Gets user from session, and wraps permissions into user 142 Gets user from session, and wraps permissions into user
97 @param session: 143 @param session:
98 """ 144 """
99 user = session.get('hg_app_user', AuthUser()) 145 user = session.get('hg_app_user', AuthUser())
146
100 if user.is_authenticated: 147 if user.is_authenticated:
101 sa = meta.Session 148 user = fill_perms(user)
102 user.permissions = sa.query(Repo2Perm)\ 149
103 .filter(Repo2Perm.user_id == user.user_id).all() 150 session['hg_app_user'] = user
104 151 session.save()
105 return user 152 return user
106 153
107 #=============================================================================== 154 #===============================================================================
108 # DECORATORS 155 # CHECK DECORATORS
109 #=============================================================================== 156 #===============================================================================
110 class LoginRequired(object): 157 class LoginRequired(object):
111 """ 158 """
112 Must be logged in to execute this function else redirect to login page 159 Must be logged in to execute this function else redirect to login page
113 """ 160 """
114 def __init__(self): 161
115 pass
116
117 def __call__(self, func): 162 def __call__(self, func):
118
119 @wraps(func) 163 @wraps(func)
120 def _wrapper(*fargs, **fkwargs): 164 def _wrapper(*fargs, **fkwargs):
121 user = session.get('hg_app_user', AuthUser()) 165 user = session.get('hg_app_user', AuthUser())
122 log.info('Checking login required for user:%s', user.username) 166 log.debug('Checking login required for user:%s', user.username)
123 if user.is_authenticated: 167 if user.is_authenticated:
124 log.info('user %s is authenticated', user.username) 168 log.debug('user %s is authenticated', user.username)
125 func(*fargs) 169 func(*fargs)
126 else: 170 else:
127 logging.info('user %s not authenticated', user.username) 171 log.warn('user %s not authenticated', user.username)
128 logging.info('redirecting to login page') 172 log.debug('redirecting to login page')
129 return redirect(url('login_home')) 173 return redirect(url('login_home'))
130 174
131 return _wrapper 175 return _wrapper
132 176
133 class PermsDecorator(object): 177 class PermsDecorator(object):
178 """
179 Base class for decorators
180 """
181
182 def __init__(self, *required_perms):
183 available_perms = config['available_permissions']
184 for perm in required_perms:
185 if perm not in available_perms:
186 raise Exception("'%s' permission is not defined" % perm)
187 self.required_perms = set(required_perms)
188 self.user_perms = None
189
190 def __call__(self, func):
191 @wraps(func)
192 def _wrapper(*fargs, **fkwargs):
193 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
194 log.debug('checking %s permissions %s for %s',
195 self.__class__.__name__, self.required_perms, func.__name__)
196
197 if self.check_permissions():
198 log.debug('Permission granted for %s', func.__name__)
199 return func(*fargs)
200
201 else:
202 log.warning('Permission denied for %s', func.__name__)
203 #redirect with forbidden ret code
204 return abort(403)
205 return _wrapper
206
207
208 def check_permissions(self):
209 """
210 Dummy function for overriding
211 """
212 raise Exception('You have to write this function in child class')
213
214 class HasPermissionAllDecorator(PermsDecorator):
215 """
216 Checks for access permission for all given predicates. All of them have to
217 be meet in order to fulfill the request
218 """
219
220 def check_permissions(self):
221 if self.required_perms.issubset(self.user_perms['global']):
222 return True
223 return False
224
225
226 class HasPermissionAnyDecorator(PermsDecorator):
227 """
228 Checks for access permission for any of given predicates. In order to
229 fulfill the request any of predicates must be meet
230 """
231
232 def check_permissions(self):
233 if self.required_perms.intersection(self.user_perms['global']):
234 return True
235 return False
236
237 class HasRepoPermissionAllDecorator(PermsDecorator):
238 """
239 Checks for access permission for all given predicates for specific
240 repository. All of them have to be meet in order to fulfill the request
241 """
242
243 def check_permissions(self):
244 repo_name = get_repo_slug(request)
245 user_perms = set([self.user_perms['repositories'][repo_name]])
246 if self.required_perms.issubset(user_perms):
247 return True
248 return False
249
250
251 class HasRepoPermissionAnyDecorator(PermsDecorator):
252 """
253 Checks for access permission for any of given predicates for specific
254 repository. In order to fulfill the request any of predicates must be meet
255 """
256
257 def check_permissions(self):
258 repo_name = get_repo_slug(request)
259
260 user_perms = set([self.user_perms['repositories'][repo_name]])
261 if self.required_perms.intersection(user_perms):
262 return True
263 return False
264 #===============================================================================
265 # CHECK FUNCTIONS
266 #===============================================================================
267
268 class PermsFunction(object):
269 """
270 Base function for other check functions
271 """
134 272
135 def __init__(self, *perms): 273 def __init__(self, *perms):
136 available_perms = g.available_permissions 274 available_perms = config['available_permissions']
275
137 for perm in perms: 276 for perm in perms:
138 if perm not in available_perms: 277 if perm not in available_perms:
139 raise Exception("'%s' permission in not defined" % perm) 278 raise Exception("'%s' permission in not defined" % perm)
140 self.required_perms = set(perms) 279 self.required_perms = set(perms)
141 self.user_perms = set([])#propagate this list from somewhere. 280 self.user_perms = None
142 281 self.granted_for = ''
143 def __call__(self, func): 282 self.repo_name = None
144 @wraps(func) 283
145 def _wrapper(*args, **kwargs): 284 def __call__(self, check_Location=''):
146 logging.info('checking %s permissions %s for %s', 285 user = session['hg_app_user']
147 self.__class__.__name__[-3:], self.required_perms, func.__name__) 286 self.user_perms = user.permissions
148 287 self.granted_for = user.username
149 if self.check_permissions(): 288 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
150 logging.info('Permission granted for %s', func.__name__) 289
151 return func(*args, **kwargs) 290 if self.check_permissions():
152 291 log.debug('Permission granted for %s @%s', self.granted_for,
153 else: 292 check_Location)
154 logging.warning('Permission denied for %s', func.__name__) 293 return True
155 #redirect with forbidden ret code 294
156 return redirect(url('access_denied'), 403) 295 else:
157 return _wrapper 296 log.warning('Permission denied for %s @%s', self.granted_for,
158 297 check_Location)
159 298 return False
299
160 def check_permissions(self): 300 def check_permissions(self):
161 """ 301 """
162 Dummy function for overiding 302 Dummy function for overriding
163 """ 303 """
164 raise Exception('You have to write this function in child class') 304 raise Exception('You have to write this function in child class')
165 305
166 class CheckPermissionAll(PermsDecorator): 306 class HasPermissionAll(PermsFunction):
167 """ 307 def check_permissions(self):
168 Checks for access permission for all given predicates. All of them have to 308 if self.required_perms.issubset(self.user_perms['global']):
169 be meet in order to fulfill the request 309 return True
170 """ 310 return False
171 311
172 def check_permissions(self): 312 class HasPermissionAny(PermsFunction):
313 def check_permissions(self):
314 if self.required_perms.intersection(self.user_perms['global']):
315 return True
316 return False
317
318 class HasRepoPermissionAll(PermsFunction):
319
320 def __call__(self, repo_name=None, check_Location=''):
321 self.repo_name = repo_name
322 return super(HasRepoPermissionAll, self).__call__(check_Location)
323
324 def check_permissions(self):
325 if not self.repo_name:
326 self.repo_name = get_repo_slug(request)
327
328 self.user_perms = set([self.user_perms['repositories']\
329 .get(self.repo_name)])
330 self.granted_for = self.repo_name
173 if self.required_perms.issubset(self.user_perms): 331 if self.required_perms.issubset(self.user_perms):
174 return True 332 return True
175 return False 333 return False
176 334
177 335 class HasRepoPermissionAny(PermsFunction):
178 class CheckPermissionAny(PermsDecorator): 336
179 """ 337
180 Checks for access permission for any of given predicates. In order to 338 def __call__(self, repo_name=None, check_Location=''):
181 fulfill the request any of predicates must be meet 339 self.repo_name = repo_name
182 """ 340 return super(HasRepoPermissionAny, self).__call__(check_Location)
183 341
184 def check_permissions(self): 342 def check_permissions(self):
343 if not self.repo_name:
344 self.repo_name = get_repo_slug(request)
345
346 self.user_perms = set([self.user_perms['repositories']\
347 .get(self.repo_name)])
348 self.granted_for = self.repo_name
185 if self.required_perms.intersection(self.user_perms): 349 if self.required_perms.intersection(self.user_perms):
186 return True 350 return True
187 return False 351 return False
188 352
189 353 #===============================================================================
190 354 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
355 #===============================================================================
356
357 class HasPermissionAnyMiddleware(object):
358 def __init__(self, *perms):
359 self.required_perms = set(perms)
360
361 def __call__(self, user, repo_name):
362 usr = AuthUser()
363 usr.user_id = user.user_id
364 usr.username = user.username
365 usr.is_admin = user.admin
366
367 try:
368 self.user_perms = set([fill_perms(usr)\
369 .permissions['repositories'][repo_name]])
370 except:
371 self.user_perms = set()
372 self.granted_for = ''
373 self.username = user.username
374 self.repo_name = repo_name
375 return self.check_permissions()
376
377 def check_permissions(self):
378 log.debug('checking mercurial protocol '
379 'permissions for user:%s repository:%s',
380 self.username, self.repo_name)
381 if self.required_perms.intersection(self.user_perms):
382 log.debug('permission granted')
383 return True
384 log.debug('permission denied')
385 return False