Mercurial > public > src > rhodecode
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 |