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