##// END OF EJS Templates
fixed issue with sessions that lead to redirection loops
marcink -
r2045:5b12cbae beta
parent child Browse files
Show More
@@ -1,169 +1,169 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.login
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Login controller for rhodeocode
7 7
8 8 :created_on: Apr 22, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import formencode
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons.i18n.translation import _
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34
35 35 import rhodecode.lib.helpers as h
36 36 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.model.db import User
39 39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 40 from rhodecode.model.user import UserModel
41 41 from rhodecode.model.meta import Session
42 42
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class LoginController(BaseController):
48 48
49 49 def __before__(self):
50 50 super(LoginController, self).__before__()
51 51
52 52 def index(self):
53 53 # redirect if already logged in
54 54 c.came_from = request.GET.get('came_from', None)
55 55
56 56 if self.rhodecode_user.is_authenticated \
57 57 and self.rhodecode_user.username != 'default':
58 58
59 59 return redirect(url('home'))
60 60
61 61 if request.POST:
62 62 # import Login Form validator class
63 63 login_form = LoginForm()
64 64 try:
65 65 c.form_result = login_form.to_python(dict(request.POST))
66 66 # form checks for username/password, now we're authenticated
67 67 username = c.form_result['username']
68 68 user = User.get_by_username(username, case_insensitive=True)
69 69 auth_user = AuthUser(user.user_id)
70 70 auth_user.set_authenticated()
71 71 cs = auth_user.get_cookie_store()
72 72 session['rhodecode_user'] = cs
73 73 # If they want to be remembered, update the cookie
74 74 if c.form_result['remember'] is not False:
75 75 session.cookie_expires = False
76 session._set_cookie_values()
76 session._set_cookie_values()
77 77 session._update_cookie_out()
78 78 session.save()
79 79
80 80 log.info('user %s is now authenticated and stored in '
81 81 'session, session attrs %s' % (username, cs))
82 82 user.update_lastlogin()
83 83 Session.commit()
84 84
85 85 if c.came_from:
86 86 return redirect(c.came_from)
87 87 else:
88 88 return redirect(url('home'))
89 89
90 90 except formencode.Invalid, errors:
91 91 return htmlfill.render(
92 92 render('/login.html'),
93 93 defaults=errors.value,
94 94 errors=errors.error_dict or {},
95 95 prefix_error=False,
96 96 encoding="UTF-8")
97 97
98 98 return render('/login.html')
99 99
100 100 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
101 101 'hg.register.manual_activate')
102 102 def register(self):
103 103 c.auto_active = False
104 104 for perm in User.get_by_username('default').user_perms:
105 105 if perm.permission.permission_name == 'hg.register.auto_activate':
106 106 c.auto_active = True
107 107 break
108 108
109 109 if request.POST:
110 110
111 111 register_form = RegisterForm()()
112 112 try:
113 113 form_result = register_form.to_python(dict(request.POST))
114 114 form_result['active'] = c.auto_active
115 115 UserModel().create_registration(form_result)
116 116 h.flash(_('You have successfully registered into rhodecode'),
117 117 category='success')
118 118 Session.commit()
119 119 return redirect(url('login_home'))
120 120
121 121 except formencode.Invalid, errors:
122 122 return htmlfill.render(
123 123 render('/register.html'),
124 124 defaults=errors.value,
125 125 errors=errors.error_dict or {},
126 126 prefix_error=False,
127 127 encoding="UTF-8")
128 128
129 129 return render('/register.html')
130 130
131 131 def password_reset(self):
132 132 if request.POST:
133 133 password_reset_form = PasswordResetForm()()
134 134 try:
135 135 form_result = password_reset_form.to_python(dict(request.POST))
136 136 UserModel().reset_password_link(form_result)
137 137 h.flash(_('Your password reset link was sent'),
138 138 category='success')
139 139 return redirect(url('login_home'))
140 140
141 141 except formencode.Invalid, errors:
142 142 return htmlfill.render(
143 143 render('/password_reset.html'),
144 144 defaults=errors.value,
145 145 errors=errors.error_dict or {},
146 146 prefix_error=False,
147 147 encoding="UTF-8")
148 148
149 149 return render('/password_reset.html')
150 150
151 151 def password_reset_confirmation(self):
152 152 if request.GET and request.GET.get('key'):
153 153 try:
154 154 user = User.get_by_api_key(request.GET.get('key'))
155 155 data = dict(email=user.email)
156 156 UserModel().reset_password(data)
157 157 h.flash(_('Your password reset was successful, '
158 158 'new password has been sent to your email'),
159 159 category='success')
160 160 except Exception, e:
161 161 log.error(e)
162 162 return redirect(url('reset_password'))
163 163
164 164 return redirect(url('login_home'))
165 165
166 166 def logout(self):
167 167 session.delete()
168 168 log.info('Logging out and deleting session for user')
169 169 redirect(url('home'))
@@ -1,806 +1,809 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 39 from rhodecode.model.meta import Session
40 40
41 41 if __platform__ in PLATFORM_WIN:
42 42 from hashlib import sha256
43 43 if __platform__ in PLATFORM_OTHERS:
44 44 import bcrypt
45 45
46 46 from rhodecode.lib import str2bool, safe_unicode
47 47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 49 from rhodecode.lib.auth_ldap import AuthLdap
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.user import UserModel
53 53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class PasswordGenerator(object):
59 59 """
60 60 This is a simple class for generating password from different sets of
61 61 characters
62 62 usage::
63 63
64 64 passwd_gen = PasswordGenerator()
65 65 #print 8-letter password containing only big and small letters
66 66 of alphabet
67 67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 68 """
69 69 ALPHABETS_NUM = r'''1234567890'''
70 70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79 79
80 80 def __init__(self, passwd=''):
81 81 self.passwd = passwd
82 82
83 83 def gen_password(self, length, type_=None):
84 84 if type_ is None:
85 85 type_ = self.ALPHABETS_FULL
86 86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
87 87 return self.passwd
88 88
89 89
90 90 class RhodeCodeCrypto(object):
91 91
92 92 @classmethod
93 93 def hash_string(cls, str_):
94 94 """
95 95 Cryptographic function used for password hashing based on pybcrypt
96 96 or pycrypto in windows
97 97
98 98 :param password: password to hash
99 99 """
100 100 if __platform__ in PLATFORM_WIN:
101 101 return sha256(str_).hexdigest()
102 102 elif __platform__ in PLATFORM_OTHERS:
103 103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
104 104 else:
105 105 raise Exception('Unknown or unsupported platform %s' \
106 106 % __platform__)
107 107
108 108 @classmethod
109 109 def hash_check(cls, password, hashed):
110 110 """
111 111 Checks matching password with it's hashed value, runs different
112 112 implementation based on platform it runs on
113 113
114 114 :param password: password
115 115 :param hashed: password in hashed form
116 116 """
117 117
118 118 if __platform__ in PLATFORM_WIN:
119 119 return sha256(password).hexdigest() == hashed
120 120 elif __platform__ in PLATFORM_OTHERS:
121 121 return bcrypt.hashpw(password, hashed) == hashed
122 122 else:
123 123 raise Exception('Unknown or unsupported platform %s' \
124 124 % __platform__)
125 125
126 126
127 127 def get_crypt_password(password):
128 128 return RhodeCodeCrypto.hash_string(password)
129 129
130 130
131 131 def check_password(password, hashed):
132 132 return RhodeCodeCrypto.hash_check(password, hashed)
133 133
134 134
135 135 def generate_api_key(str_, salt=None):
136 136 """
137 137 Generates API KEY from given string
138 138
139 139 :param str_:
140 140 :param salt:
141 141 """
142 142
143 143 if salt is None:
144 144 salt = _RandomNameSequence().next()
145 145
146 146 return hashlib.sha1(str_ + salt).hexdigest()
147 147
148 148
149 149 def authfunc(environ, username, password):
150 150 """
151 151 Dummy authentication wrapper function used in Mercurial and Git for
152 152 access control.
153 153
154 154 :param environ: needed only for using in Basic auth
155 155 """
156 156 return authenticate(username, password)
157 157
158 158
159 159 def authenticate(username, password):
160 160 """
161 161 Authentication function used for access control,
162 162 firstly checks for db authentication then if ldap is enabled for ldap
163 163 authentication, also creates ldap user if not in database
164 164
165 165 :param username: username
166 166 :param password: password
167 167 """
168 168
169 169 user_model = UserModel()
170 170 user = User.get_by_username(username)
171 171
172 172 log.debug('Authenticating user using RhodeCode account')
173 173 if user is not None and not user.ldap_dn:
174 174 if user.active:
175 175 if user.username == 'default' and user.active:
176 176 log.info('user %s authenticated correctly as anonymous user' %
177 177 username)
178 178 return True
179 179
180 180 elif user.username == username and check_password(password,
181 181 user.password):
182 182 log.info('user %s authenticated correctly' % username)
183 183 return True
184 184 else:
185 185 log.warning('user %s tried auth but is disabled' % username)
186 186
187 187 else:
188 188 log.debug('Regular authentication failed')
189 189 user_obj = User.get_by_username(username, case_insensitive=True)
190 190
191 191 if user_obj is not None and not user_obj.ldap_dn:
192 192 log.debug('this user already exists as non ldap')
193 193 return False
194 194
195 195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 196 #======================================================================
197 197 # FALLBACK TO LDAP AUTH IF ENABLE
198 198 #======================================================================
199 199 if str2bool(ldap_settings.get('ldap_active')):
200 200 log.debug("Authenticating user using ldap")
201 201 kwargs = {
202 202 'server': ldap_settings.get('ldap_host', ''),
203 203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 204 'port': ldap_settings.get('ldap_port'),
205 205 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 209 'ldap_filter': ldap_settings.get('ldap_filter'),
210 210 'search_scope': ldap_settings.get('ldap_search_scope'),
211 211 'attr_login': ldap_settings.get('ldap_attr_login'),
212 212 'ldap_version': 3,
213 213 }
214 214 log.debug('Checking for ldap authentication')
215 215 try:
216 216 aldap = AuthLdap(**kwargs)
217 217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 218 password)
219 219 log.debug('Got ldap DN response %s' % user_dn)
220 220
221 221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 222 .get(k), [''])[0]
223 223
224 224 user_attrs = {
225 225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 227 'email': get_ldap_attr('ldap_attr_email'),
228 228 }
229 229
230 230 # don't store LDAP password since we don't need it. Override
231 231 # with some random generated password
232 232 _password = PasswordGenerator().gen_password(length=8)
233 233 # create this user on the fly if it doesn't exist in rhodecode
234 234 # database
235 235 if user_model.create_ldap(username, _password, user_dn,
236 236 user_attrs):
237 237 log.info('created new ldap user %s' % username)
238 238
239 239 Session.commit()
240 240 return True
241 241 except (LdapUsernameError, LdapPasswordError,):
242 242 pass
243 243 except (Exception,):
244 244 log.error(traceback.format_exc())
245 245 pass
246 246 return False
247 247
248 248
249 249 def login_container_auth(username):
250 250 user = User.get_by_username(username)
251 251 if user is None:
252 252 user_attrs = {
253 253 'name': username,
254 254 'lastname': None,
255 255 'email': None,
256 256 }
257 257 user = UserModel().create_for_container_auth(username, user_attrs)
258 258 if not user:
259 259 return None
260 260 log.info('User %s was created by container authentication' % username)
261 261
262 262 if not user.active:
263 263 return None
264 264
265 265 user.update_lastlogin()
266 266 Session.commit()
267 267
268 268 log.debug('User %s is now logged in by container authentication',
269 269 user.username)
270 270 return user
271 271
272 272
273 273 def get_container_username(environ, config):
274 274 username = None
275 275
276 276 if str2bool(config.get('container_auth_enabled', False)):
277 277 from paste.httpheaders import REMOTE_USER
278 278 username = REMOTE_USER(environ)
279 279
280 280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 281 username = environ.get('HTTP_X_FORWARDED_USER')
282 282
283 283 if username:
284 284 # Removing realm and domain from username
285 285 username = username.partition('@')[0]
286 286 username = username.rpartition('\\')[2]
287 287 log.debug('Received username %s from container' % username)
288 288
289 289 return username
290 290
291 291
292 292 class CookieStoreWrapper(object):
293 293
294 294 def __init__(self, cookie_store):
295 295 self.cookie_store = cookie_store
296 296
297 297 def __repr__(self):
298 298 return 'CookieStore<%s>' % (self.cookie_store)
299 299
300 300 def get(self, key, other=None):
301 301 if isinstance(self.cookie_store, dict):
302 302 return self.cookie_store.get(key, other)
303 303 elif isinstance(self.cookie_store, AuthUser):
304 304 return self.cookie_store.__dict__.get(key, other)
305 305
306 306
307 307 class AuthUser(object):
308 308 """
309 309 A simple object that handles all attributes of user in RhodeCode
310 310
311 311 It does lookup based on API key,given user, or user present in session
312 312 Then it fills all required information for such user. It also checks if
313 313 anonymous access is enabled and if so, it returns default user as logged
314 314 in
315 315 """
316 316
317 317 def __init__(self, user_id=None, api_key=None, username=None):
318 318
319 319 self.user_id = user_id
320 320 self.api_key = None
321 321 self.username = username
322 322
323 323 self.name = ''
324 324 self.lastname = ''
325 325 self.email = ''
326 326 self.is_authenticated = False
327 327 self.admin = False
328 328 self.permissions = {}
329 329 self._api_key = api_key
330 330 self.propagate_data()
331 331 self._instance = None
332 332
333 333 def propagate_data(self):
334 334 user_model = UserModel()
335 335 self.anonymous_user = User.get_by_username('default', cache=True)
336 336 is_user_loaded = False
337 337
338 338 # try go get user by api key
339 339 if self._api_key and self._api_key != self.anonymous_user.api_key:
340 340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
341 341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
342 342 # lookup by userid
343 343 elif (self.user_id is not None and
344 344 self.user_id != self.anonymous_user.user_id):
345 345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
346 346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
347 347 # lookup by username
348 348 elif self.username and \
349 349 str2bool(config.get('container_auth_enabled', False)):
350 350
351 351 log.debug('Auth User lookup by USER NAME %s' % self.username)
352 352 dbuser = login_container_auth(self.username)
353 353 if dbuser is not None:
354 354 for k, v in dbuser.get_dict().items():
355 355 setattr(self, k, v)
356 356 self.set_authenticated()
357 357 is_user_loaded = True
358 else:
359 log.debug('No data in %s that could been used to log in' % self)
358 360
359 361 if not is_user_loaded:
360 362 # if we cannot authenticate user try anonymous
361 363 if self.anonymous_user.active is True:
362 364 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
363 365 # then we set this user is logged in
364 366 self.is_authenticated = True
365 367 else:
366 368 self.user_id = None
367 369 self.username = None
368 370 self.is_authenticated = False
369 371
370 372 if not self.username:
371 373 self.username = 'None'
372 374
373 375 log.debug('Auth User is now %s' % self)
374 376 user_model.fill_perms(self)
375 377
376 378 @property
377 379 def is_admin(self):
378 380 return self.admin
379 381
380 382 def __repr__(self):
381 383 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
382 384 self.is_authenticated)
383 385
384 386 def set_authenticated(self, authenticated=True):
385 387 if self.user_id != self.anonymous_user.user_id:
386 388 self.is_authenticated = authenticated
387 389
388 390 def get_cookie_store(self):
389 391 return {'username': self.username,
390 392 'user_id': self.user_id,
391 393 'is_authenticated': self.is_authenticated}
392 394
393 395 @classmethod
394 396 def from_cookie_store(cls, cookie_store):
395 397 """
396 398 Creates AuthUser from a cookie store
397 399
398 400 :param cls:
399 401 :param cookie_store:
400 402 """
401 403 user_id = cookie_store.get('user_id')
402 404 username = cookie_store.get('username')
403 405 api_key = cookie_store.get('api_key')
404 406 return AuthUser(user_id, api_key, username)
405 407
406 408
407 409 def set_available_permissions(config):
408 410 """
409 411 This function will propagate pylons globals with all available defined
410 412 permission given in db. We don't want to check each time from db for new
411 413 permissions since adding a new permission also requires application restart
412 414 ie. to decorate new views with the newly created permission
413 415
414 416 :param config: current pylons config instance
415 417
416 418 """
417 419 log.info('getting information about all available permissions')
418 420 try:
419 421 sa = meta.Session
420 422 all_perms = sa.query(Permission).all()
421 423 except Exception:
422 424 pass
423 425 finally:
424 426 meta.Session.remove()
425 427
426 428 config['available_permissions'] = [x.permission_name for x in all_perms]
427 429
428 430
429 431 #==============================================================================
430 432 # CHECK DECORATORS
431 433 #==============================================================================
432 434 class LoginRequired(object):
433 435 """
434 436 Must be logged in to execute this function else
435 437 redirect to login page
436 438
437 439 :param api_access: if enabled this checks only for valid auth token
438 440 and grants access based on valid token
439 441 """
440 442
441 443 def __init__(self, api_access=False):
442 444 self.api_access = api_access
443 445
444 446 def __call__(self, func):
445 447 return decorator(self.__wrapper, func)
446 448
447 449 def __wrapper(self, func, *fargs, **fkwargs):
448 450 cls = fargs[0]
449 451 user = cls.rhodecode_user
450 452
451 453 api_access_ok = False
452 454 if self.api_access:
453 455 log.debug('Checking API KEY access for %s' % cls)
454 456 if user.api_key == request.GET.get('api_key'):
455 457 api_access_ok = True
456 458 else:
457 459 log.debug("API KEY token not valid")
458 460 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
459 461 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
460 462 if user.is_authenticated or api_access_ok:
461 463 log.info('user %s is authenticated and granted access to %s' % (
462 464 user.username, loc)
463 465 )
464 466 return func(*fargs, **fkwargs)
465 467 else:
466 468 log.warn('user %s NOT authenticated on func: %s' % (
467 469 user, loc)
468 470 )
469 471 p = url.current()
470 472
471 473 log.debug('redirecting to login page with %s' % p)
472 474 return redirect(url('login_home', came_from=p))
473 475
474 476
475 477 class NotAnonymous(object):
476 478 """
477 479 Must be logged in to execute this function else
478 480 redirect to login page"""
479 481
480 482 def __call__(self, func):
481 483 return decorator(self.__wrapper, func)
482 484
483 485 def __wrapper(self, func, *fargs, **fkwargs):
484 486 cls = fargs[0]
485 487 self.user = cls.rhodecode_user
486 488
487 489 log.debug('Checking if user is not anonymous @%s' % cls)
488 490
489 491 anonymous = self.user.username == 'default'
490 492
491 493 if anonymous:
492 494 p = url.current()
493 495
494 496 import rhodecode.lib.helpers as h
495 497 h.flash(_('You need to be a registered user to '
496 498 'perform this action'),
497 499 category='warning')
498 500 return redirect(url('login_home', came_from=p))
499 501 else:
500 502 return func(*fargs, **fkwargs)
501 503
502 504
503 505 class PermsDecorator(object):
504 506 """Base class for controller decorators"""
505 507
506 508 def __init__(self, *required_perms):
507 509 available_perms = config['available_permissions']
508 510 for perm in required_perms:
509 511 if perm not in available_perms:
510 512 raise Exception("'%s' permission is not defined" % perm)
511 513 self.required_perms = set(required_perms)
512 514 self.user_perms = None
513 515
514 516 def __call__(self, func):
515 517 return decorator(self.__wrapper, func)
516 518
517 519 def __wrapper(self, func, *fargs, **fkwargs):
518 520 cls = fargs[0]
519 521 self.user = cls.rhodecode_user
520 522 self.user_perms = self.user.permissions
521 523 log.debug('checking %s permissions %s for %s %s',
522 524 self.__class__.__name__, self.required_perms, cls,
523 525 self.user)
524 526
525 527 if self.check_permissions():
526 528 log.debug('Permission granted for %s %s' % (cls, self.user))
527 529 return func(*fargs, **fkwargs)
528 530
529 531 else:
530 532 log.debug('Permission denied for %s %s' % (cls, self.user))
531 533 anonymous = self.user.username == 'default'
532 534
533 535 if anonymous:
534 536 p = url.current()
535 537
536 538 import rhodecode.lib.helpers as h
537 539 h.flash(_('You need to be a signed in to '
538 540 'view this page'),
539 541 category='warning')
540 542 return redirect(url('login_home', came_from=p))
541 543
542 544 else:
543 545 # redirect with forbidden ret code
544 546 return abort(403)
545 547
546 548 def check_permissions(self):
547 549 """Dummy function for overriding"""
548 550 raise Exception('You have to write this function in child class')
549 551
550 552
551 553 class HasPermissionAllDecorator(PermsDecorator):
552 554 """
553 555 Checks for access permission for all given predicates. All of them
554 556 have to be meet in order to fulfill the request
555 557 """
556 558
557 559 def check_permissions(self):
558 560 if self.required_perms.issubset(self.user_perms.get('global')):
559 561 return True
560 562 return False
561 563
562 564
563 565 class HasPermissionAnyDecorator(PermsDecorator):
564 566 """
565 567 Checks for access permission for any of given predicates. In order to
566 568 fulfill the request any of predicates must be meet
567 569 """
568 570
569 571 def check_permissions(self):
570 572 if self.required_perms.intersection(self.user_perms.get('global')):
571 573 return True
572 574 return False
573 575
574 576
575 577 class HasRepoPermissionAllDecorator(PermsDecorator):
576 578 """
577 579 Checks for access permission for all given predicates for specific
578 580 repository. All of them have to be meet in order to fulfill the request
579 581 """
580 582
581 583 def check_permissions(self):
582 584 repo_name = get_repo_slug(request)
583 585 try:
584 586 user_perms = set([self.user_perms['repositories'][repo_name]])
585 587 except KeyError:
586 588 return False
587 589 if self.required_perms.issubset(user_perms):
588 590 return True
589 591 return False
590 592
591 593
592 594 class HasRepoPermissionAnyDecorator(PermsDecorator):
593 595 """
594 596 Checks for access permission for any of given predicates for specific
595 597 repository. In order to fulfill the request any of predicates must be meet
596 598 """
597 599
598 600 def check_permissions(self):
599 601 repo_name = get_repo_slug(request)
600 602
601 603 try:
602 604 user_perms = set([self.user_perms['repositories'][repo_name]])
603 605 except KeyError:
604 606 return False
605 607 if self.required_perms.intersection(user_perms):
606 608 return True
607 609 return False
608 610
609 611
610 612 class HasReposGroupPermissionAllDecorator(PermsDecorator):
611 613 """
612 614 Checks for access permission for all given predicates for specific
613 615 repository. All of them have to be meet in order to fulfill the request
614 616 """
615 617
616 618 def check_permissions(self):
617 619 group_name = get_repos_group_slug(request)
618 620 try:
619 621 user_perms = set([self.user_perms['repositories_groups'][group_name]])
620 622 except KeyError:
621 623 return False
622 624 if self.required_perms.issubset(user_perms):
623 625 return True
624 626 return False
625 627
626 628
627 629 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
628 630 """
629 631 Checks for access permission for any of given predicates for specific
630 632 repository. In order to fulfill the request any of predicates must be meet
631 633 """
632 634
633 635 def check_permissions(self):
634 636 group_name = get_repos_group_slug(request)
635 637
636 638 try:
637 639 user_perms = set([self.user_perms['repositories_groups'][group_name]])
638 640 except KeyError:
639 641 return False
640 642 if self.required_perms.intersection(user_perms):
641 643 return True
642 644 return False
643 645
644 646
645 647 #==============================================================================
646 648 # CHECK FUNCTIONS
647 649 #==============================================================================
648 650 class PermsFunction(object):
649 651 """Base function for other check functions"""
650 652
651 653 def __init__(self, *perms):
652 654 available_perms = config['available_permissions']
653 655
654 656 for perm in perms:
655 657 if perm not in available_perms:
656 658 raise Exception("'%s' permission in not defined" % perm)
657 659 self.required_perms = set(perms)
658 660 self.user_perms = None
659 661 self.granted_for = ''
660 662 self.repo_name = None
661 663
662 664 def __call__(self, check_Location=''):
663 665 user = request.user
666 log.debug('checking %s %s %s', self.__class__.__name__,
667 self.required_perms, user)
664 668 if not user:
669 log.debug('Empty request user')
665 670 return False
666 671 self.user_perms = user.permissions
667 672 self.granted_for = user
668 log.debug('checking %s %s %s', self.__class__.__name__,
669 self.required_perms, user)
670 673
671 674 if self.check_permissions():
672 675 log.debug('Permission granted %s @ %s', self.granted_for,
673 676 check_Location or 'unspecified location')
674 677 return True
675 678
676 679 else:
677 680 log.debug('Permission denied for %s @ %s', self.granted_for,
678 681 check_Location or 'unspecified location')
679 682 return False
680 683
681 684 def check_permissions(self):
682 685 """Dummy function for overriding"""
683 686 raise Exception('You have to write this function in child class')
684 687
685 688
686 689 class HasPermissionAll(PermsFunction):
687 690 def check_permissions(self):
688 691 if self.required_perms.issubset(self.user_perms.get('global')):
689 692 return True
690 693 return False
691 694
692 695
693 696 class HasPermissionAny(PermsFunction):
694 697 def check_permissions(self):
695 698 if self.required_perms.intersection(self.user_perms.get('global')):
696 699 return True
697 700 return False
698 701
699 702
700 703 class HasRepoPermissionAll(PermsFunction):
701 704
702 705 def __call__(self, repo_name=None, check_Location=''):
703 706 self.repo_name = repo_name
704 707 return super(HasRepoPermissionAll, self).__call__(check_Location)
705 708
706 709 def check_permissions(self):
707 710 if not self.repo_name:
708 711 self.repo_name = get_repo_slug(request)
709 712
710 713 try:
711 714 self.user_perms = set(
712 715 [self.user_perms['repositories'][self.repo_name]]
713 716 )
714 717 except KeyError:
715 718 return False
716 719 self.granted_for = self.repo_name
717 720 if self.required_perms.issubset(self.user_perms):
718 721 return True
719 722 return False
720 723
721 724
722 725 class HasRepoPermissionAny(PermsFunction):
723 726
724 727 def __call__(self, repo_name=None, check_Location=''):
725 728 self.repo_name = repo_name
726 729 return super(HasRepoPermissionAny, self).__call__(check_Location)
727 730
728 731 def check_permissions(self):
729 732 if not self.repo_name:
730 733 self.repo_name = get_repo_slug(request)
731 734
732 735 try:
733 736 self.user_perms = set(
734 737 [self.user_perms['repositories'][self.repo_name]]
735 738 )
736 739 except KeyError:
737 740 return False
738 741 self.granted_for = self.repo_name
739 742 if self.required_perms.intersection(self.user_perms):
740 743 return True
741 744 return False
742 745
743 746
744 747 class HasReposGroupPermissionAny(PermsFunction):
745 748 def __call__(self, group_name=None, check_Location=''):
746 749 self.group_name = group_name
747 750 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
748 751
749 752 def check_permissions(self):
750 753 try:
751 754 self.user_perms = set(
752 755 [self.user_perms['repositories_groups'][self.group_name]]
753 756 )
754 757 except KeyError:
755 758 return False
756 759 self.granted_for = self.repo_name
757 760 if self.required_perms.intersection(self.user_perms):
758 761 return True
759 762 return False
760 763
761 764
762 765 class HasReposGroupPermissionAll(PermsFunction):
763 766 def __call__(self, group_name=None, check_Location=''):
764 767 self.group_name = group_name
765 768 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
766 769
767 770 def check_permissions(self):
768 771 try:
769 772 self.user_perms = set(
770 773 [self.user_perms['repositories_groups'][self.group_name]]
771 774 )
772 775 except KeyError:
773 776 return False
774 777 self.granted_for = self.repo_name
775 778 if self.required_perms.issubset(self.user_perms):
776 779 return True
777 780 return False
778 781
779 782
780 783 #==============================================================================
781 784 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
782 785 #==============================================================================
783 786 class HasPermissionAnyMiddleware(object):
784 787 def __init__(self, *perms):
785 788 self.required_perms = set(perms)
786 789
787 790 def __call__(self, user, repo_name):
788 791 usr = AuthUser(user.user_id)
789 792 try:
790 793 self.user_perms = set([usr.permissions['repositories'][repo_name]])
791 794 except:
792 795 self.user_perms = set()
793 796 self.granted_for = ''
794 797 self.username = user.username
795 798 self.repo_name = repo_name
796 799 return self.check_permissions()
797 800
798 801 def check_permissions(self):
799 802 log.debug('checking mercurial protocol '
800 803 'permissions %s for user:%s repository:%s', self.user_perms,
801 804 self.username, self.repo_name)
802 805 if self.required_perms.intersection(self.user_perms):
803 806 log.debug('permission granted')
804 807 return True
805 808 log.debug('permission denied')
806 809 return False
@@ -1,184 +1,183 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10
11 11 from pylons import config, tmpl_context as c, request, session, url
12 12 from pylons.controllers import WSGIController
13 13 from pylons.controllers.util import redirect
14 14 from pylons.templating import render_mako as render
15 15
16 16 from rhodecode import __version__, BACKENDS
17 17
18 18 from rhodecode.lib import str2bool, safe_unicode
19 19 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
20 20 HasPermissionAnyMiddleware, CookieStoreWrapper
21 21 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
22 22 from rhodecode.model import meta
23 23
24 24 from rhodecode.model.db import Repository
25 25 from rhodecode.model.notification import NotificationModel
26 26 from rhodecode.model.scm import ScmModel
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 class BaseVCSController(object):
32 32
33 33 def __init__(self, application, config):
34 34 self.application = application
35 35 self.config = config
36 36 # base path of repo locations
37 37 self.basepath = self.config['base_path']
38 38 #authenticate this mercurial request using authfunc
39 39 self.authenticate = AuthBasicAuthenticator('', authfunc)
40 40 self.ipaddr = '0.0.0.0'
41 41
42 42 def _handle_request(self, environ, start_response):
43 43 raise NotImplementedError()
44 44
45 45 def _get_by_id(self, repo_name):
46 46 """
47 47 Get's a special pattern _<ID> from clone url and tries to replace it
48 48 with a repository_name for support of _<ID> non changable urls
49 49
50 50 :param repo_name:
51 51 """
52 52 try:
53 53 data = repo_name.split('/')
54 54 if len(data) >= 2:
55 55 by_id = data[1].split('_')
56 56 if len(by_id) == 2 and by_id[1].isdigit():
57 57 _repo_name = Repository.get(by_id[1]).repo_name
58 58 data[1] = _repo_name
59 59 except:
60 60 log.debug('Failed to extract repo_name from id %s' % (
61 61 traceback.format_exc()
62 62 )
63 63 )
64 64
65 65 return '/'.join(data)
66 66
67 67 def _invalidate_cache(self, repo_name):
68 68 """
69 69 Set's cache for this repository for invalidation on next access
70 70
71 71 :param repo_name: full repo name, also a cache key
72 72 """
73 73 invalidate_cache('get_repo_cached_%s' % repo_name)
74 74
75 75 def _check_permission(self, action, user, repo_name):
76 76 """
77 77 Checks permissions using action (push/pull) user and repository
78 78 name
79 79
80 80 :param action: push or pull action
81 81 :param user: user instance
82 82 :param repo_name: repository name
83 83 """
84 84 if action == 'push':
85 85 if not HasPermissionAnyMiddleware('repository.write',
86 86 'repository.admin')(user,
87 87 repo_name):
88 88 return False
89 89
90 90 else:
91 91 #any other action need at least read permission
92 92 if not HasPermissionAnyMiddleware('repository.read',
93 93 'repository.write',
94 94 'repository.admin')(user,
95 95 repo_name):
96 96 return False
97 97
98 98 return True
99 99
100 100 def __call__(self, environ, start_response):
101 101 start = time.time()
102 102 try:
103 103 return self._handle_request(environ, start_response)
104 104 finally:
105 105 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
106 106 log.debug('Request time: %.3fs' % (time.time() - start))
107 107 meta.Session.remove()
108 108
109 109
110 110 class BaseController(WSGIController):
111 111
112 112 def __before__(self):
113 113 c.rhodecode_version = __version__
114 114 c.rhodecode_instanceid = config.get('instance_id')
115 115 c.rhodecode_name = config.get('rhodecode_title')
116 116 c.use_gravatar = str2bool(config.get('use_gravatar'))
117 117 c.ga_code = config.get('rhodecode_ga_code')
118 118 c.repo_name = get_repo_slug(request)
119 119 c.backends = BACKENDS.keys()
120 120 c.unread_notifications = NotificationModel()\
121 121 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
122 122 self.cut_off_limit = int(config.get('cut_off_limit'))
123 123
124 124 self.sa = meta.Session
125 125 self.scm_model = ScmModel(self.sa)
126 126
127 127 def __call__(self, environ, start_response):
128 128 """Invoke the Controller"""
129 129 # WSGIController.__call__ dispatches to the Controller method
130 130 # the request is routed to. This routing information is
131 131 # available in environ['pylons.routes_dict']
132 132 start = time.time()
133 133 try:
134 134 # make sure that we update permissions each time we call controller
135 135 api_key = request.GET.get('api_key')
136 136 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
137 137 user_id = cookie_store.get('user_id', None)
138 138 username = get_container_username(environ, config)
139
140 139 auth_user = AuthUser(user_id, api_key, username)
141 140 request.user = auth_user
142 141 self.rhodecode_user = c.rhodecode_user = auth_user
143 142 if not self.rhodecode_user.is_authenticated and \
144 143 self.rhodecode_user.user_id is not None:
145 144 self.rhodecode_user.set_authenticated(
146 145 cookie_store.get('is_authenticated')
147 146 )
148 147 log.info('User: %s accessed %s' % (
149 148 auth_user, safe_unicode(environ.get('PATH_INFO')))
150 149 )
151 150 return WSGIController.__call__(self, environ, start_response)
152 151 finally:
153 152 log.info('Request to %s time: %.3fs' % (
154 153 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
155 154 )
156 155 meta.Session.remove()
157 156
158 157
159 158 class BaseRepoController(BaseController):
160 159 """
161 160 Base class for controllers responsible for loading all needed data for
162 161 repository loaded items are
163 162
164 163 c.rhodecode_repo: instance of scm repository
165 164 c.rhodecode_db_repo: instance of db
166 165 c.repository_followers: number of followers
167 166 c.repository_forks: number of forks
168 167 """
169 168
170 169 def __before__(self):
171 170 super(BaseRepoController, self).__before__()
172 171 if c.repo_name:
173 172
174 173 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
175 174 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
176 175
177 176 if c.rhodecode_repo is None:
178 177 log.error('%s this repository is present in database but it '
179 178 'cannot be created as an scm instance', c.repo_name)
180 179
181 180 redirect(url('home'))
182 181
183 182 c.repository_followers = self.scm_model.get_followers(c.repo_name)
184 183 c.repository_forks = self.scm_model.get_forks(c.repo_name)
General Comments 0
You need to be logged in to leave comments. Login now