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