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