##// END OF EJS Templates
auth: fix failure when editing inactive users...
Mads Kiilerich -
r7638:1f831a8a default
parent child Browse files
Show More
@@ -1,886 +1,872 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.auth
15 kallithea.lib.auth
16 ~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~
17
17
18 authentication and permission libraries
18 authentication and permission libraries
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 4, 2010
22 :created_on: Apr 4, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import collections
32 import collections
33
33
34 from decorator import decorator
34 from decorator import decorator
35
35
36 from tg import request, session
36 from tg import request, session
37 from tg.i18n import ugettext as _
37 from tg.i18n import ugettext as _
38 from webhelpers.pylonslib import secure_form
38 from webhelpers.pylonslib import secure_form
39 from sqlalchemy.orm.exc import ObjectDeletedError
39 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm import joinedload
40 from sqlalchemy.orm import joinedload
41 from webob.exc import HTTPFound, HTTPBadRequest, HTTPForbidden, HTTPMethodNotAllowed
41 from webob.exc import HTTPFound, HTTPBadRequest, HTTPForbidden, HTTPMethodNotAllowed
42
42
43 from kallithea import __platform__, is_windows, is_unix
43 from kallithea import __platform__, is_windows, is_unix
44 from kallithea.config.routing import url
44 from kallithea.config.routing import url
45 from kallithea.lib.vcs.utils.lazy import LazyProperty
45 from kallithea.lib.vcs.utils.lazy import LazyProperty
46 from kallithea.model.meta import Session
46 from kallithea.model.meta import Session
47 from kallithea.model.user import UserModel
47 from kallithea.model.user import UserModel
48 from kallithea.model.db import User, Repository, Permission, \
48 from kallithea.model.db import User, Repository, Permission, \
49 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
49 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
50 RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
50 RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
51 UserGroup, UserApiKeys
51 UserGroup, UserApiKeys
52
52
53 from kallithea.lib.utils2 import safe_str, safe_unicode, aslist
53 from kallithea.lib.utils2 import safe_str, safe_unicode, aslist
54 from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \
54 from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \
55 get_user_group_slug, conditional_cache
55 get_user_group_slug, conditional_cache
56 from kallithea.lib.caching_query import FromCache
56 from kallithea.lib.caching_query import FromCache
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class PasswordGenerator(object):
62 class PasswordGenerator(object):
63 """
63 """
64 This is a simple class for generating password from different sets of
64 This is a simple class for generating password from different sets of
65 characters
65 characters
66 usage::
66 usage::
67
67
68 passwd_gen = PasswordGenerator()
68 passwd_gen = PasswordGenerator()
69 #print 8-letter password containing only big and small letters
69 #print 8-letter password containing only big and small letters
70 of alphabet
70 of alphabet
71 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
71 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
72 """
72 """
73 ALPHABETS_NUM = r'''1234567890'''
73 ALPHABETS_NUM = r'''1234567890'''
74 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
74 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
75 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
75 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
76 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
76 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
77 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
77 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
78 + ALPHABETS_NUM + ALPHABETS_SPECIAL
78 + ALPHABETS_NUM + ALPHABETS_SPECIAL
79 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
79 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
80 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
80 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
81 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
81 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
82 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
82 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
83
83
84 def gen_password(self, length, alphabet=ALPHABETS_FULL):
84 def gen_password(self, length, alphabet=ALPHABETS_FULL):
85 assert len(alphabet) <= 256, alphabet
85 assert len(alphabet) <= 256, alphabet
86 l = []
86 l = []
87 while len(l) < length:
87 while len(l) < length:
88 i = ord(os.urandom(1))
88 i = ord(os.urandom(1))
89 if i < len(alphabet):
89 if i < len(alphabet):
90 l.append(alphabet[i])
90 l.append(alphabet[i])
91 return ''.join(l)
91 return ''.join(l)
92
92
93
93
94 def get_crypt_password(password):
94 def get_crypt_password(password):
95 """
95 """
96 Cryptographic function used for password hashing based on pybcrypt
96 Cryptographic function used for password hashing based on pybcrypt
97 or Python's own OpenSSL wrapper on windows
97 or Python's own OpenSSL wrapper on windows
98
98
99 :param password: password to hash
99 :param password: password to hash
100 """
100 """
101 if is_windows:
101 if is_windows:
102 return hashlib.sha256(password).hexdigest()
102 return hashlib.sha256(password).hexdigest()
103 elif is_unix:
103 elif is_unix:
104 import bcrypt
104 import bcrypt
105 return bcrypt.hashpw(safe_str(password), bcrypt.gensalt(10))
105 return bcrypt.hashpw(safe_str(password), bcrypt.gensalt(10))
106 else:
106 else:
107 raise Exception('Unknown or unsupported platform %s'
107 raise Exception('Unknown or unsupported platform %s'
108 % __platform__)
108 % __platform__)
109
109
110
110
111 def check_password(password, hashed):
111 def check_password(password, hashed):
112 """
112 """
113 Checks matching password with it's hashed value, runs different
113 Checks matching password with it's hashed value, runs different
114 implementation based on platform it runs on
114 implementation based on platform it runs on
115
115
116 :param password: password
116 :param password: password
117 :param hashed: password in hashed form
117 :param hashed: password in hashed form
118 """
118 """
119
119
120 if is_windows:
120 if is_windows:
121 return hashlib.sha256(password).hexdigest() == hashed
121 return hashlib.sha256(password).hexdigest() == hashed
122 elif is_unix:
122 elif is_unix:
123 import bcrypt
123 import bcrypt
124 try:
124 try:
125 return bcrypt.checkpw(safe_str(password), safe_str(hashed))
125 return bcrypt.checkpw(safe_str(password), safe_str(hashed))
126 except ValueError as e:
126 except ValueError as e:
127 # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
127 # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
128 log.error('error from bcrypt checking password: %s', e)
128 log.error('error from bcrypt checking password: %s', e)
129 return False
129 return False
130 else:
130 else:
131 raise Exception('Unknown or unsupported platform %s'
131 raise Exception('Unknown or unsupported platform %s'
132 % __platform__)
132 % __platform__)
133
133
134
134
135 def _cached_perms_data(user_id, user_is_admin):
135 def _cached_perms_data(user_id, user_is_admin):
136 RK = 'repositories'
136 RK = 'repositories'
137 GK = 'repositories_groups'
137 GK = 'repositories_groups'
138 UK = 'user_groups'
138 UK = 'user_groups'
139 GLOBAL = 'global'
139 GLOBAL = 'global'
140 PERM_WEIGHTS = Permission.PERM_WEIGHTS
140 PERM_WEIGHTS = Permission.PERM_WEIGHTS
141 permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()}
141 permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()}
142
142
143 def bump_permission(kind, key, new_perm):
143 def bump_permission(kind, key, new_perm):
144 """Add a new permission for kind and key.
144 """Add a new permission for kind and key.
145 Assuming the permissions are comparable, set the new permission if it
145 Assuming the permissions are comparable, set the new permission if it
146 has higher weight, else drop it and keep the old permission.
146 has higher weight, else drop it and keep the old permission.
147 """
147 """
148 cur_perm = permissions[kind][key]
148 cur_perm = permissions[kind][key]
149 new_perm_val = PERM_WEIGHTS[new_perm]
149 new_perm_val = PERM_WEIGHTS[new_perm]
150 cur_perm_val = PERM_WEIGHTS[cur_perm]
150 cur_perm_val = PERM_WEIGHTS[cur_perm]
151 if new_perm_val > cur_perm_val:
151 if new_perm_val > cur_perm_val:
152 permissions[kind][key] = new_perm
152 permissions[kind][key] = new_perm
153
153
154 #======================================================================
154 #======================================================================
155 # fetch default permissions
155 # fetch default permissions
156 #======================================================================
156 #======================================================================
157 default_user = User.get_by_username('default', cache=True)
157 default_user = User.get_by_username('default', cache=True)
158 default_user_id = default_user.user_id
158 default_user_id = default_user.user_id
159
159
160 default_repo_perms = Permission.get_default_perms(default_user_id)
160 default_repo_perms = Permission.get_default_perms(default_user_id)
161 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
161 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
162 default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
162 default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
163
163
164 if user_is_admin:
164 if user_is_admin:
165 #==================================================================
165 #==================================================================
166 # admin users have all rights;
166 # admin users have all rights;
167 # based on default permissions, just set everything to admin
167 # based on default permissions, just set everything to admin
168 #==================================================================
168 #==================================================================
169 permissions[GLOBAL].add('hg.admin')
169 permissions[GLOBAL].add('hg.admin')
170 permissions[GLOBAL].add('hg.create.write_on_repogroup.true')
170 permissions[GLOBAL].add('hg.create.write_on_repogroup.true')
171
171
172 # repositories
172 # repositories
173 for perm in default_repo_perms:
173 for perm in default_repo_perms:
174 r_k = perm.UserRepoToPerm.repository.repo_name
174 r_k = perm.UserRepoToPerm.repository.repo_name
175 p = 'repository.admin'
175 p = 'repository.admin'
176 permissions[RK][r_k] = p
176 permissions[RK][r_k] = p
177
177
178 # repository groups
178 # repository groups
179 for perm in default_repo_groups_perms:
179 for perm in default_repo_groups_perms:
180 rg_k = perm.UserRepoGroupToPerm.group.group_name
180 rg_k = perm.UserRepoGroupToPerm.group.group_name
181 p = 'group.admin'
181 p = 'group.admin'
182 permissions[GK][rg_k] = p
182 permissions[GK][rg_k] = p
183
183
184 # user groups
184 # user groups
185 for perm in default_user_group_perms:
185 for perm in default_user_group_perms:
186 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
186 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
187 p = 'usergroup.admin'
187 p = 'usergroup.admin'
188 permissions[UK][u_k] = p
188 permissions[UK][u_k] = p
189 return permissions
189 return permissions
190
190
191 #==================================================================
191 #==================================================================
192 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
192 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
193 #==================================================================
193 #==================================================================
194
194
195 # default global permissions taken from the default user
195 # default global permissions taken from the default user
196 default_global_perms = UserToPerm.query() \
196 default_global_perms = UserToPerm.query() \
197 .filter(UserToPerm.user_id == default_user_id) \
197 .filter(UserToPerm.user_id == default_user_id) \
198 .options(joinedload(UserToPerm.permission))
198 .options(joinedload(UserToPerm.permission))
199
199
200 for perm in default_global_perms:
200 for perm in default_global_perms:
201 permissions[GLOBAL].add(perm.permission.permission_name)
201 permissions[GLOBAL].add(perm.permission.permission_name)
202
202
203 # defaults for repositories, taken from default user
203 # defaults for repositories, taken from default user
204 for perm in default_repo_perms:
204 for perm in default_repo_perms:
205 r_k = perm.UserRepoToPerm.repository.repo_name
205 r_k = perm.UserRepoToPerm.repository.repo_name
206 if perm.Repository.owner_id == user_id:
206 if perm.Repository.owner_id == user_id:
207 p = 'repository.admin'
207 p = 'repository.admin'
208 elif perm.Repository.private:
208 elif perm.Repository.private:
209 p = 'repository.none'
209 p = 'repository.none'
210 else:
210 else:
211 p = perm.Permission.permission_name
211 p = perm.Permission.permission_name
212 permissions[RK][r_k] = p
212 permissions[RK][r_k] = p
213
213
214 # defaults for repository groups taken from default user permission
214 # defaults for repository groups taken from default user permission
215 # on given group
215 # on given group
216 for perm in default_repo_groups_perms:
216 for perm in default_repo_groups_perms:
217 rg_k = perm.UserRepoGroupToPerm.group.group_name
217 rg_k = perm.UserRepoGroupToPerm.group.group_name
218 p = perm.Permission.permission_name
218 p = perm.Permission.permission_name
219 permissions[GK][rg_k] = p
219 permissions[GK][rg_k] = p
220
220
221 # defaults for user groups taken from default user permission
221 # defaults for user groups taken from default user permission
222 # on given user group
222 # on given user group
223 for perm in default_user_group_perms:
223 for perm in default_user_group_perms:
224 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
224 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
225 p = perm.Permission.permission_name
225 p = perm.Permission.permission_name
226 permissions[UK][u_k] = p
226 permissions[UK][u_k] = p
227
227
228 #======================================================================
228 #======================================================================
229 # !! Augment GLOBALS with user permissions if any found !!
229 # !! Augment GLOBALS with user permissions if any found !!
230 #======================================================================
230 #======================================================================
231
231
232 # USER GROUPS comes first
232 # USER GROUPS comes first
233 # user group global permissions
233 # user group global permissions
234 user_perms_from_users_groups = Session().query(UserGroupToPerm) \
234 user_perms_from_users_groups = Session().query(UserGroupToPerm) \
235 .options(joinedload(UserGroupToPerm.permission)) \
235 .options(joinedload(UserGroupToPerm.permission)) \
236 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
236 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
237 UserGroupMember.users_group_id)) \
237 UserGroupMember.users_group_id)) \
238 .filter(UserGroupMember.user_id == user_id) \
238 .filter(UserGroupMember.user_id == user_id) \
239 .join((UserGroup, UserGroupMember.users_group_id ==
239 .join((UserGroup, UserGroupMember.users_group_id ==
240 UserGroup.users_group_id)) \
240 UserGroup.users_group_id)) \
241 .filter(UserGroup.users_group_active == True) \
241 .filter(UserGroup.users_group_active == True) \
242 .order_by(UserGroupToPerm.users_group_id) \
242 .order_by(UserGroupToPerm.users_group_id) \
243 .all()
243 .all()
244 # need to group here by groups since user can be in more than
244 # need to group here by groups since user can be in more than
245 # one group
245 # one group
246 _grouped = [[x, list(y)] for x, y in
246 _grouped = [[x, list(y)] for x, y in
247 itertools.groupby(user_perms_from_users_groups,
247 itertools.groupby(user_perms_from_users_groups,
248 lambda x:x.users_group)]
248 lambda x:x.users_group)]
249 for gr, perms in _grouped:
249 for gr, perms in _grouped:
250 for perm in perms:
250 for perm in perms:
251 permissions[GLOBAL].add(perm.permission.permission_name)
251 permissions[GLOBAL].add(perm.permission.permission_name)
252
252
253 # user specific global permissions
253 # user specific global permissions
254 user_perms = Session().query(UserToPerm) \
254 user_perms = Session().query(UserToPerm) \
255 .options(joinedload(UserToPerm.permission)) \
255 .options(joinedload(UserToPerm.permission)) \
256 .filter(UserToPerm.user_id == user_id).all()
256 .filter(UserToPerm.user_id == user_id).all()
257
257
258 for perm in user_perms:
258 for perm in user_perms:
259 permissions[GLOBAL].add(perm.permission.permission_name)
259 permissions[GLOBAL].add(perm.permission.permission_name)
260
260
261 # for each kind of global permissions, only keep the one with heighest weight
261 # for each kind of global permissions, only keep the one with heighest weight
262 kind_max_perm = {}
262 kind_max_perm = {}
263 for perm in sorted(permissions[GLOBAL], key=lambda n: PERM_WEIGHTS[n]):
263 for perm in sorted(permissions[GLOBAL], key=lambda n: PERM_WEIGHTS[n]):
264 kind = perm.rsplit('.', 1)[0]
264 kind = perm.rsplit('.', 1)[0]
265 kind_max_perm[kind] = perm
265 kind_max_perm[kind] = perm
266 permissions[GLOBAL] = set(kind_max_perm.values())
266 permissions[GLOBAL] = set(kind_max_perm.values())
267 ## END GLOBAL PERMISSIONS
267 ## END GLOBAL PERMISSIONS
268
268
269 #======================================================================
269 #======================================================================
270 # !! PERMISSIONS FOR REPOSITORIES !!
270 # !! PERMISSIONS FOR REPOSITORIES !!
271 #======================================================================
271 #======================================================================
272 #======================================================================
272 #======================================================================
273 # check if user is part of user groups for this repository and
273 # check if user is part of user groups for this repository and
274 # fill in his permission from it.
274 # fill in his permission from it.
275 #======================================================================
275 #======================================================================
276
276
277 # user group for repositories permissions
277 # user group for repositories permissions
278 user_repo_perms_from_users_groups = \
278 user_repo_perms_from_users_groups = \
279 Session().query(UserGroupRepoToPerm, Permission, Repository,) \
279 Session().query(UserGroupRepoToPerm, Permission, Repository,) \
280 .join((Repository, UserGroupRepoToPerm.repository_id ==
280 .join((Repository, UserGroupRepoToPerm.repository_id ==
281 Repository.repo_id)) \
281 Repository.repo_id)) \
282 .join((Permission, UserGroupRepoToPerm.permission_id ==
282 .join((Permission, UserGroupRepoToPerm.permission_id ==
283 Permission.permission_id)) \
283 Permission.permission_id)) \
284 .join((UserGroup, UserGroupRepoToPerm.users_group_id ==
284 .join((UserGroup, UserGroupRepoToPerm.users_group_id ==
285 UserGroup.users_group_id)) \
285 UserGroup.users_group_id)) \
286 .filter(UserGroup.users_group_active == True) \
286 .filter(UserGroup.users_group_active == True) \
287 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
287 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
288 UserGroupMember.users_group_id)) \
288 UserGroupMember.users_group_id)) \
289 .filter(UserGroupMember.user_id == user_id) \
289 .filter(UserGroupMember.user_id == user_id) \
290 .all()
290 .all()
291
291
292 for perm in user_repo_perms_from_users_groups:
292 for perm in user_repo_perms_from_users_groups:
293 bump_permission(RK,
293 bump_permission(RK,
294 perm.UserGroupRepoToPerm.repository.repo_name,
294 perm.UserGroupRepoToPerm.repository.repo_name,
295 perm.Permission.permission_name)
295 perm.Permission.permission_name)
296
296
297 # user permissions for repositories
297 # user permissions for repositories
298 user_repo_perms = Permission.get_default_perms(user_id)
298 user_repo_perms = Permission.get_default_perms(user_id)
299 for perm in user_repo_perms:
299 for perm in user_repo_perms:
300 bump_permission(RK,
300 bump_permission(RK,
301 perm.UserRepoToPerm.repository.repo_name,
301 perm.UserRepoToPerm.repository.repo_name,
302 perm.Permission.permission_name)
302 perm.Permission.permission_name)
303
303
304 #======================================================================
304 #======================================================================
305 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
305 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
306 #======================================================================
306 #======================================================================
307 #======================================================================
307 #======================================================================
308 # check if user is part of user groups for this repository groups and
308 # check if user is part of user groups for this repository groups and
309 # fill in his permission from it.
309 # fill in his permission from it.
310 #======================================================================
310 #======================================================================
311 # user group for repo groups permissions
311 # user group for repo groups permissions
312 user_repo_group_perms_from_users_groups = \
312 user_repo_group_perms_from_users_groups = \
313 Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup) \
313 Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup) \
314 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)) \
314 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)) \
315 .join((Permission, UserGroupRepoGroupToPerm.permission_id
315 .join((Permission, UserGroupRepoGroupToPerm.permission_id
316 == Permission.permission_id)) \
316 == Permission.permission_id)) \
317 .join((UserGroup, UserGroupRepoGroupToPerm.users_group_id ==
317 .join((UserGroup, UserGroupRepoGroupToPerm.users_group_id ==
318 UserGroup.users_group_id)) \
318 UserGroup.users_group_id)) \
319 .filter(UserGroup.users_group_active == True) \
319 .filter(UserGroup.users_group_active == True) \
320 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
320 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
321 == UserGroupMember.users_group_id)) \
321 == UserGroupMember.users_group_id)) \
322 .filter(UserGroupMember.user_id == user_id) \
322 .filter(UserGroupMember.user_id == user_id) \
323 .all()
323 .all()
324
324
325 for perm in user_repo_group_perms_from_users_groups:
325 for perm in user_repo_group_perms_from_users_groups:
326 bump_permission(GK,
326 bump_permission(GK,
327 perm.UserGroupRepoGroupToPerm.group.group_name,
327 perm.UserGroupRepoGroupToPerm.group.group_name,
328 perm.Permission.permission_name)
328 perm.Permission.permission_name)
329
329
330 # user explicit permissions for repository groups
330 # user explicit permissions for repository groups
331 user_repo_groups_perms = Permission.get_default_group_perms(user_id)
331 user_repo_groups_perms = Permission.get_default_group_perms(user_id)
332 for perm in user_repo_groups_perms:
332 for perm in user_repo_groups_perms:
333 bump_permission(GK,
333 bump_permission(GK,
334 perm.UserRepoGroupToPerm.group.group_name,
334 perm.UserRepoGroupToPerm.group.group_name,
335 perm.Permission.permission_name)
335 perm.Permission.permission_name)
336
336
337 #======================================================================
337 #======================================================================
338 # !! PERMISSIONS FOR USER GROUPS !!
338 # !! PERMISSIONS FOR USER GROUPS !!
339 #======================================================================
339 #======================================================================
340 # user group for user group permissions
340 # user group for user group permissions
341 user_group_user_groups_perms = \
341 user_group_user_groups_perms = \
342 Session().query(UserGroupUserGroupToPerm, Permission, UserGroup) \
342 Session().query(UserGroupUserGroupToPerm, Permission, UserGroup) \
343 .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
343 .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
344 == UserGroup.users_group_id)) \
344 == UserGroup.users_group_id)) \
345 .join((Permission, UserGroupUserGroupToPerm.permission_id
345 .join((Permission, UserGroupUserGroupToPerm.permission_id
346 == Permission.permission_id)) \
346 == Permission.permission_id)) \
347 .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
347 .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
348 == UserGroupMember.users_group_id)) \
348 == UserGroupMember.users_group_id)) \
349 .filter(UserGroupMember.user_id == user_id) \
349 .filter(UserGroupMember.user_id == user_id) \
350 .join((UserGroup, UserGroupMember.users_group_id ==
350 .join((UserGroup, UserGroupMember.users_group_id ==
351 UserGroup.users_group_id), aliased=True, from_joinpoint=True) \
351 UserGroup.users_group_id), aliased=True, from_joinpoint=True) \
352 .filter(UserGroup.users_group_active == True) \
352 .filter(UserGroup.users_group_active == True) \
353 .all()
353 .all()
354
354
355 for perm in user_group_user_groups_perms:
355 for perm in user_group_user_groups_perms:
356 bump_permission(UK,
356 bump_permission(UK,
357 perm.UserGroupUserGroupToPerm.target_user_group.users_group_name,
357 perm.UserGroupUserGroupToPerm.target_user_group.users_group_name,
358 perm.Permission.permission_name)
358 perm.Permission.permission_name)
359
359
360 # user explicit permission for user groups
360 # user explicit permission for user groups
361 user_user_groups_perms = Permission.get_default_user_group_perms(user_id)
361 user_user_groups_perms = Permission.get_default_user_group_perms(user_id)
362 for perm in user_user_groups_perms:
362 for perm in user_user_groups_perms:
363 bump_permission(UK,
363 bump_permission(UK,
364 perm.UserUserGroupToPerm.user_group.users_group_name,
364 perm.UserUserGroupToPerm.user_group.users_group_name,
365 perm.Permission.permission_name)
365 perm.Permission.permission_name)
366
366
367 return permissions
367 return permissions
368
368
369
369
370 class AuthUser(object):
370 class AuthUser(object):
371 """
371 """
372 Represents a Kallithea user, including various authentication and
372 Represents a Kallithea user, including various authentication and
373 authorization information. Typically used to store the current user,
373 authorization information. Typically used to store the current user,
374 but is also used as a generic user information data structure in
374 but is also used as a generic user information data structure in
375 parts of the code, e.g. user management.
375 parts of the code, e.g. user management.
376
376
377 Constructed from a database `User` object, a user ID or cookie dict,
377 Constructed from a database `User` object, a user ID or cookie dict,
378 it looks up the user (if needed) and copies all attributes to itself,
378 it looks up the user (if needed) and copies all attributes to itself,
379 adding various non-persistent data. If lookup fails but anonymous
379 adding various non-persistent data. If lookup fails but anonymous
380 access to Kallithea is enabled, the default user is loaded instead.
380 access to Kallithea is enabled, the default user is loaded instead.
381
381
382 `AuthUser` does not by itself authenticate users. It's up to other parts of
382 `AuthUser` does not by itself authenticate users. It's up to other parts of
383 the code to check e.g. if a supplied password is correct, and if so, trust
383 the code to check e.g. if a supplied password is correct, and if so, trust
384 the AuthUser object as an authenticated user.
384 the AuthUser object as an authenticated user.
385
385
386 However, `AuthUser` does refuse to load a user that is not `active`.
386 However, `AuthUser` does refuse to load a user that is not `active`.
387
387
388 Note that Kallithea distinguishes between the default user (an actual
388 Note that Kallithea distinguishes between the default user (an actual
389 user in the database with username "default") and "no user" (no actual
389 user in the database with username "default") and "no user" (no actual
390 User object, AuthUser filled with blank values and username "None").
390 User object, AuthUser filled with blank values and username "None").
391
391
392 If the default user is active, that will always be used instead of
392 If the default user is active, that will always be used instead of
393 "no user". On the other hand, if the default user is disabled (and
393 "no user". On the other hand, if the default user is disabled (and
394 there is no login information), we instead get "no user"; this should
394 there is no login information), we instead get "no user"; this should
395 only happen on the login page (as all other requests are redirected).
395 only happen on the login page (as all other requests are redirected).
396
396
397 `is_default_user` specifically checks if the AuthUser is the user named
397 `is_default_user` specifically checks if the AuthUser is the user named
398 "default". Use `is_anonymous` to check for both "default" and "no user".
398 "default". Use `is_anonymous` to check for both "default" and "no user".
399 """
399 """
400
400
401 @classmethod
401 @classmethod
402 def make(cls, dbuser=None, is_external_auth=False, ip_addr=None):
402 def make(cls, dbuser=None, is_external_auth=False, ip_addr=None):
403 """Create an AuthUser to be authenticated ... or return None if user for some reason can't be authenticated.
403 """Create an AuthUser to be authenticated ... or return None if user for some reason can't be authenticated.
404 Checks that a non-None dbuser is provided, is active, and that the IP address is ok.
404 Checks that a non-None dbuser is provided, is active, and that the IP address is ok.
405 """
405 """
406 assert ip_addr is not None
406 assert ip_addr is not None
407 if dbuser is None:
407 if dbuser is None:
408 log.info('No db user for authentication')
408 log.info('No db user for authentication')
409 return None
409 return None
410 if not dbuser.active:
410 if not dbuser.active:
411 log.info('Db user %s not active', dbuser.username)
411 log.info('Db user %s not active', dbuser.username)
412 return None
412 return None
413 allowed_ips = AuthUser.get_allowed_ips(dbuser.user_id, cache=True)
413 allowed_ips = AuthUser.get_allowed_ips(dbuser.user_id, cache=True)
414 if not check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
414 if not check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
415 log.info('Access for %s from %s forbidden - not in %s', dbuser.username, ip_addr, allowed_ips)
415 log.info('Access for %s from %s forbidden - not in %s', dbuser.username, ip_addr, allowed_ips)
416 return None
416 return None
417 return cls(dbuser=dbuser, is_external_auth=is_external_auth)
417 return cls(dbuser=dbuser, is_external_auth=is_external_auth)
418
418
419 def __init__(self, user_id=None, dbuser=None, is_external_auth=False):
419 def __init__(self, user_id=None, dbuser=None, is_external_auth=False):
420 self.is_external_auth = is_external_auth # container auth - don't show logout option
420 self.is_external_auth = is_external_auth # container auth - don't show logout option
421
421
422 # These attributes will be overridden by fill_data, below, unless the
422 # These attributes will be overridden by fill_data, below, unless the
423 # requested user cannot be found and the default anonymous user is
423 # requested user cannot be found and the default anonymous user is
424 # not enabled.
424 # not enabled.
425 self.user_id = None
425 self.user_id = None
426 self.username = None
426 self.username = None
427 self.api_key = None
427 self.api_key = None
428 self.name = ''
428 self.name = ''
429 self.lastname = ''
429 self.lastname = ''
430 self.email = ''
430 self.email = ''
431 self.admin = False
431 self.admin = False
432
432
433 # Look up database user, if necessary.
433 # Look up database user, if necessary.
434 if user_id is not None:
434 if user_id is not None:
435 assert dbuser is None
435 assert dbuser is None
436 log.debug('Auth User lookup by USER ID %s', user_id)
436 log.debug('Auth User lookup by USER ID %s', user_id)
437 dbuser = UserModel().get(user_id)
437 dbuser = UserModel().get(user_id)
438 assert dbuser is not None
438 assert dbuser is not None
439 else:
439 else:
440 assert dbuser is not None
440 assert dbuser is not None
441 log.debug('Auth User lookup by database user %s', dbuser)
441 log.debug('Auth User lookup by database user %s', dbuser)
442
442
443 if self._fill_data(dbuser):
443 log.debug('filling %s data', dbuser)
444 self.is_default_user = dbuser.is_default_user
444 self.is_anonymous = dbuser.is_default_user
445 else:
445 if dbuser.is_default_user and not dbuser.active:
446 assert dbuser.is_default_user
447 assert not self.username
448 self.username = 'None'
446 self.username = 'None'
449 self.is_default_user = False
447 self.is_default_user = False
450 self.is_anonymous = dbuser.is_default_user
448 else:
451
449 # copy non-confidential database fields from a `db.User` to this `AuthUser`.
452 log.debug('Auth User is now %s', self)
453
454 def _fill_data(self, dbuser):
455 """
456 Copies database fields from a `db.User` to this `AuthUser`. Does
457 not copy `api_keys` and `permissions` attributes.
458
459 Checks that `dbuser` is `active` (and not None) before copying;
460 returns True on success.
461 """
462 if dbuser is not None and dbuser.active:
463 log.debug('filling %s data', dbuser)
464 for k, v in dbuser.get_dict().iteritems():
450 for k, v in dbuser.get_dict().iteritems():
465 assert k not in ['api_keys', 'permissions']
451 assert k not in ['api_keys', 'permissions']
466 setattr(self, k, v)
452 setattr(self, k, v)
467 return True
453 self.is_default_user = dbuser.is_default_user
468 return False
454 log.debug('Auth User is now %s', self)
469
455
470 @LazyProperty
456 @LazyProperty
471 def permissions(self):
457 def permissions(self):
472 return self.__get_perms(user=self, cache=False)
458 return self.__get_perms(user=self, cache=False)
473
459
474 def has_repository_permission_level(self, repo_name, level, purpose=None):
460 def has_repository_permission_level(self, repo_name, level, purpose=None):
475 required_perms = {
461 required_perms = {
476 'read': ['repository.read', 'repository.write', 'repository.admin'],
462 'read': ['repository.read', 'repository.write', 'repository.admin'],
477 'write': ['repository.write', 'repository.admin'],
463 'write': ['repository.write', 'repository.admin'],
478 'admin': ['repository.admin'],
464 'admin': ['repository.admin'],
479 }[level]
465 }[level]
480 actual_perm = self.permissions['repositories'].get(repo_name)
466 actual_perm = self.permissions['repositories'].get(repo_name)
481 ok = actual_perm in required_perms
467 ok = actual_perm in required_perms
482 log.debug('Checking if user %r can %r repo %r (%s): %s (has %r)',
468 log.debug('Checking if user %r can %r repo %r (%s): %s (has %r)',
483 self.username, level, repo_name, purpose, ok, actual_perm)
469 self.username, level, repo_name, purpose, ok, actual_perm)
484 return ok
470 return ok
485
471
486 def has_repository_group_permission_level(self, repo_group_name, level, purpose=None):
472 def has_repository_group_permission_level(self, repo_group_name, level, purpose=None):
487 required_perms = {
473 required_perms = {
488 'read': ['group.read', 'group.write', 'group.admin'],
474 'read': ['group.read', 'group.write', 'group.admin'],
489 'write': ['group.write', 'group.admin'],
475 'write': ['group.write', 'group.admin'],
490 'admin': ['group.admin'],
476 'admin': ['group.admin'],
491 }[level]
477 }[level]
492 actual_perm = self.permissions['repositories_groups'].get(repo_group_name)
478 actual_perm = self.permissions['repositories_groups'].get(repo_group_name)
493 ok = actual_perm in required_perms
479 ok = actual_perm in required_perms
494 log.debug('Checking if user %r can %r repo group %r (%s): %s (has %r)',
480 log.debug('Checking if user %r can %r repo group %r (%s): %s (has %r)',
495 self.username, level, repo_group_name, purpose, ok, actual_perm)
481 self.username, level, repo_group_name, purpose, ok, actual_perm)
496 return ok
482 return ok
497
483
498 def has_user_group_permission_level(self, user_group_name, level, purpose=None):
484 def has_user_group_permission_level(self, user_group_name, level, purpose=None):
499 required_perms = {
485 required_perms = {
500 'read': ['usergroup.read', 'usergroup.write', 'usergroup.admin'],
486 'read': ['usergroup.read', 'usergroup.write', 'usergroup.admin'],
501 'write': ['usergroup.write', 'usergroup.admin'],
487 'write': ['usergroup.write', 'usergroup.admin'],
502 'admin': ['usergroup.admin'],
488 'admin': ['usergroup.admin'],
503 }[level]
489 }[level]
504 actual_perm = self.permissions['user_groups'].get(user_group_name)
490 actual_perm = self.permissions['user_groups'].get(user_group_name)
505 ok = actual_perm in required_perms
491 ok = actual_perm in required_perms
506 log.debug('Checking if user %r can %r user group %r (%s): %s (has %r)',
492 log.debug('Checking if user %r can %r user group %r (%s): %s (has %r)',
507 self.username, level, user_group_name, purpose, ok, actual_perm)
493 self.username, level, user_group_name, purpose, ok, actual_perm)
508 return ok
494 return ok
509
495
510 @property
496 @property
511 def api_keys(self):
497 def api_keys(self):
512 return self._get_api_keys()
498 return self._get_api_keys()
513
499
514 def __get_perms(self, user, cache=False):
500 def __get_perms(self, user, cache=False):
515 """
501 """
516 Fills user permission attribute with permissions taken from database
502 Fills user permission attribute with permissions taken from database
517 works for permissions given for repositories, and for permissions that
503 works for permissions given for repositories, and for permissions that
518 are granted to groups
504 are granted to groups
519
505
520 :param user: `AuthUser` instance
506 :param user: `AuthUser` instance
521 """
507 """
522 user_id = user.user_id
508 user_id = user.user_id
523 user_is_admin = user.is_admin
509 user_is_admin = user.is_admin
524
510
525 log.debug('Getting PERMISSION tree')
511 log.debug('Getting PERMISSION tree')
526 compute = conditional_cache('short_term', 'cache_desc',
512 compute = conditional_cache('short_term', 'cache_desc',
527 condition=cache, func=_cached_perms_data)
513 condition=cache, func=_cached_perms_data)
528 return compute(user_id, user_is_admin)
514 return compute(user_id, user_is_admin)
529
515
530 def _get_api_keys(self):
516 def _get_api_keys(self):
531 api_keys = [self.api_key]
517 api_keys = [self.api_key]
532 for api_key in UserApiKeys.query() \
518 for api_key in UserApiKeys.query() \
533 .filter_by(user_id=self.user_id, is_expired=False):
519 .filter_by(user_id=self.user_id, is_expired=False):
534 api_keys.append(api_key.api_key)
520 api_keys.append(api_key.api_key)
535
521
536 return api_keys
522 return api_keys
537
523
538 @property
524 @property
539 def is_admin(self):
525 def is_admin(self):
540 return self.admin
526 return self.admin
541
527
542 @property
528 @property
543 def repositories_admin(self):
529 def repositories_admin(self):
544 """
530 """
545 Returns list of repositories you're an admin of
531 Returns list of repositories you're an admin of
546 """
532 """
547 return [x[0] for x in self.permissions['repositories'].iteritems()
533 return [x[0] for x in self.permissions['repositories'].iteritems()
548 if x[1] == 'repository.admin']
534 if x[1] == 'repository.admin']
549
535
550 @property
536 @property
551 def repository_groups_admin(self):
537 def repository_groups_admin(self):
552 """
538 """
553 Returns list of repository groups you're an admin of
539 Returns list of repository groups you're an admin of
554 """
540 """
555 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
541 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
556 if x[1] == 'group.admin']
542 if x[1] == 'group.admin']
557
543
558 @property
544 @property
559 def user_groups_admin(self):
545 def user_groups_admin(self):
560 """
546 """
561 Returns list of user groups you're an admin of
547 Returns list of user groups you're an admin of
562 """
548 """
563 return [x[0] for x in self.permissions['user_groups'].iteritems()
549 return [x[0] for x in self.permissions['user_groups'].iteritems()
564 if x[1] == 'usergroup.admin']
550 if x[1] == 'usergroup.admin']
565
551
566 def __repr__(self):
552 def __repr__(self):
567 return "<AuthUser('id:%s[%s]')>" % (self.user_id, self.username)
553 return "<AuthUser('id:%s[%s]')>" % (self.user_id, self.username)
568
554
569 def to_cookie(self):
555 def to_cookie(self):
570 """ Serializes this login session to a cookie `dict`. """
556 """ Serializes this login session to a cookie `dict`. """
571 return {
557 return {
572 'user_id': self.user_id,
558 'user_id': self.user_id,
573 'is_external_auth': self.is_external_auth,
559 'is_external_auth': self.is_external_auth,
574 }
560 }
575
561
576 @staticmethod
562 @staticmethod
577 def from_cookie(cookie, ip_addr):
563 def from_cookie(cookie, ip_addr):
578 """
564 """
579 Deserializes an `AuthUser` from a cookie `dict` ... or return None.
565 Deserializes an `AuthUser` from a cookie `dict` ... or return None.
580 """
566 """
581 return AuthUser.make(
567 return AuthUser.make(
582 dbuser=UserModel().get(cookie.get('user_id')),
568 dbuser=UserModel().get(cookie.get('user_id')),
583 is_external_auth=cookie.get('is_external_auth', False),
569 is_external_auth=cookie.get('is_external_auth', False),
584 ip_addr=ip_addr,
570 ip_addr=ip_addr,
585 )
571 )
586
572
587 @classmethod
573 @classmethod
588 def get_allowed_ips(cls, user_id, cache=False):
574 def get_allowed_ips(cls, user_id, cache=False):
589 _set = set()
575 _set = set()
590
576
591 default_ips = UserIpMap.query().filter(UserIpMap.user_id ==
577 default_ips = UserIpMap.query().filter(UserIpMap.user_id ==
592 User.get_default_user(cache=True).user_id)
578 User.get_default_user(cache=True).user_id)
593 if cache:
579 if cache:
594 default_ips = default_ips.options(FromCache("sql_cache_short",
580 default_ips = default_ips.options(FromCache("sql_cache_short",
595 "get_user_ips_default"))
581 "get_user_ips_default"))
596 for ip in default_ips:
582 for ip in default_ips:
597 try:
583 try:
598 _set.add(ip.ip_addr)
584 _set.add(ip.ip_addr)
599 except ObjectDeletedError:
585 except ObjectDeletedError:
600 # since we use heavy caching sometimes it happens that we get
586 # since we use heavy caching sometimes it happens that we get
601 # deleted objects here, we just skip them
587 # deleted objects here, we just skip them
602 pass
588 pass
603
589
604 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
590 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
605 if cache:
591 if cache:
606 user_ips = user_ips.options(FromCache("sql_cache_short",
592 user_ips = user_ips.options(FromCache("sql_cache_short",
607 "get_user_ips_%s" % user_id))
593 "get_user_ips_%s" % user_id))
608 for ip in user_ips:
594 for ip in user_ips:
609 try:
595 try:
610 _set.add(ip.ip_addr)
596 _set.add(ip.ip_addr)
611 except ObjectDeletedError:
597 except ObjectDeletedError:
612 # since we use heavy caching sometimes it happens that we get
598 # since we use heavy caching sometimes it happens that we get
613 # deleted objects here, we just skip them
599 # deleted objects here, we just skip them
614 pass
600 pass
615 return _set or set(['0.0.0.0/0', '::/0'])
601 return _set or set(['0.0.0.0/0', '::/0'])
616
602
617
603
618 def set_available_permissions(config):
604 def set_available_permissions(config):
619 """
605 """
620 This function will propagate globals with all available defined
606 This function will propagate globals with all available defined
621 permission given in db. We don't want to check each time from db for new
607 permission given in db. We don't want to check each time from db for new
622 permissions since adding a new permission also requires application restart
608 permissions since adding a new permission also requires application restart
623 ie. to decorate new views with the newly created permission
609 ie. to decorate new views with the newly created permission
624
610
625 :param config: current config instance
611 :param config: current config instance
626
612
627 """
613 """
628 log.info('getting information about all available permissions')
614 log.info('getting information about all available permissions')
629 try:
615 try:
630 all_perms = Session().query(Permission).all()
616 all_perms = Session().query(Permission).all()
631 config['available_permissions'] = [x.permission_name for x in all_perms]
617 config['available_permissions'] = [x.permission_name for x in all_perms]
632 finally:
618 finally:
633 Session.remove()
619 Session.remove()
634
620
635
621
636 #==============================================================================
622 #==============================================================================
637 # CHECK DECORATORS
623 # CHECK DECORATORS
638 #==============================================================================
624 #==============================================================================
639
625
640 def _redirect_to_login(message=None):
626 def _redirect_to_login(message=None):
641 """Return an exception that must be raised. It will redirect to the login
627 """Return an exception that must be raised. It will redirect to the login
642 page which will redirect back to the current URL after authentication.
628 page which will redirect back to the current URL after authentication.
643 The optional message will be shown in a flash message."""
629 The optional message will be shown in a flash message."""
644 from kallithea.lib import helpers as h
630 from kallithea.lib import helpers as h
645 if message:
631 if message:
646 h.flash(message, category='warning')
632 h.flash(message, category='warning')
647 p = request.path_qs
633 p = request.path_qs
648 log.debug('Redirecting to login page, origin: %s', p)
634 log.debug('Redirecting to login page, origin: %s', p)
649 return HTTPFound(location=url('login_home', came_from=p))
635 return HTTPFound(location=url('login_home', came_from=p))
650
636
651
637
652 # Use as decorator
638 # Use as decorator
653 class LoginRequired(object):
639 class LoginRequired(object):
654 """Client must be logged in as a valid User, or we'll redirect to the login
640 """Client must be logged in as a valid User, or we'll redirect to the login
655 page.
641 page.
656
642
657 If the "default" user is enabled and allow_default_user is true, that is
643 If the "default" user is enabled and allow_default_user is true, that is
658 considered valid too.
644 considered valid too.
659
645
660 Also checks that IP address is allowed.
646 Also checks that IP address is allowed.
661 """
647 """
662
648
663 def __init__(self, allow_default_user=False):
649 def __init__(self, allow_default_user=False):
664 self.allow_default_user = allow_default_user
650 self.allow_default_user = allow_default_user
665
651
666 def __call__(self, func):
652 def __call__(self, func):
667 return decorator(self.__wrapper, func)
653 return decorator(self.__wrapper, func)
668
654
669 def __wrapper(self, func, *fargs, **fkwargs):
655 def __wrapper(self, func, *fargs, **fkwargs):
670 controller = fargs[0]
656 controller = fargs[0]
671 user = request.authuser
657 user = request.authuser
672 loc = "%s:%s" % (controller.__class__.__name__, func.__name__)
658 loc = "%s:%s" % (controller.__class__.__name__, func.__name__)
673 log.debug('Checking access for user %s @ %s', user, loc)
659 log.debug('Checking access for user %s @ %s', user, loc)
674
660
675 # regular user authentication
661 # regular user authentication
676 if user.is_default_user:
662 if user.is_default_user:
677 if self.allow_default_user:
663 if self.allow_default_user:
678 log.info('default user @ %s', loc)
664 log.info('default user @ %s', loc)
679 return func(*fargs, **fkwargs)
665 return func(*fargs, **fkwargs)
680 log.info('default user is not accepted here @ %s', loc)
666 log.info('default user is not accepted here @ %s', loc)
681 elif user.is_anonymous: # default user is disabled and no proper authentication
667 elif user.is_anonymous: # default user is disabled and no proper authentication
682 log.warning('user is anonymous and NOT authenticated with regular auth @ %s', loc)
668 log.warning('user is anonymous and NOT authenticated with regular auth @ %s', loc)
683 else: # regular authentication
669 else: # regular authentication
684 log.info('user %s authenticated with regular auth @ %s', user, loc)
670 log.info('user %s authenticated with regular auth @ %s', user, loc)
685 return func(*fargs, **fkwargs)
671 return func(*fargs, **fkwargs)
686 raise _redirect_to_login()
672 raise _redirect_to_login()
687
673
688
674
689 # Use as decorator
675 # Use as decorator
690 class NotAnonymous(object):
676 class NotAnonymous(object):
691 """Ensures that client is not logged in as the "default" user, and
677 """Ensures that client is not logged in as the "default" user, and
692 redirects to the login page otherwise. Must be used together with
678 redirects to the login page otherwise. Must be used together with
693 LoginRequired."""
679 LoginRequired."""
694
680
695 def __call__(self, func):
681 def __call__(self, func):
696 return decorator(self.__wrapper, func)
682 return decorator(self.__wrapper, func)
697
683
698 def __wrapper(self, func, *fargs, **fkwargs):
684 def __wrapper(self, func, *fargs, **fkwargs):
699 cls = fargs[0]
685 cls = fargs[0]
700 user = request.authuser
686 user = request.authuser
701
687
702 log.debug('Checking that user %s is not anonymous @%s', user.username, cls)
688 log.debug('Checking that user %s is not anonymous @%s', user.username, cls)
703
689
704 if user.is_default_user:
690 if user.is_default_user:
705 raise _redirect_to_login(_('You need to be a registered user to '
691 raise _redirect_to_login(_('You need to be a registered user to '
706 'perform this action'))
692 'perform this action'))
707 else:
693 else:
708 return func(*fargs, **fkwargs)
694 return func(*fargs, **fkwargs)
709
695
710
696
711 class _PermsDecorator(object):
697 class _PermsDecorator(object):
712 """Base class for controller decorators with multiple permissions"""
698 """Base class for controller decorators with multiple permissions"""
713
699
714 def __init__(self, *required_perms):
700 def __init__(self, *required_perms):
715 self.required_perms = required_perms # usually very short - a list is thus fine
701 self.required_perms = required_perms # usually very short - a list is thus fine
716
702
717 def __call__(self, func):
703 def __call__(self, func):
718 return decorator(self.__wrapper, func)
704 return decorator(self.__wrapper, func)
719
705
720 def __wrapper(self, func, *fargs, **fkwargs):
706 def __wrapper(self, func, *fargs, **fkwargs):
721 cls = fargs[0]
707 cls = fargs[0]
722 user = request.authuser
708 user = request.authuser
723 log.debug('checking %s permissions %s for %s %s',
709 log.debug('checking %s permissions %s for %s %s',
724 self.__class__.__name__, self.required_perms, cls, user)
710 self.__class__.__name__, self.required_perms, cls, user)
725
711
726 if self.check_permissions(user):
712 if self.check_permissions(user):
727 log.debug('Permission granted for %s %s', cls, user)
713 log.debug('Permission granted for %s %s', cls, user)
728 return func(*fargs, **fkwargs)
714 return func(*fargs, **fkwargs)
729
715
730 else:
716 else:
731 log.info('Permission denied for %s %s', cls, user)
717 log.info('Permission denied for %s %s', cls, user)
732 if user.is_default_user:
718 if user.is_default_user:
733 raise _redirect_to_login(_('You need to be signed in to view this page'))
719 raise _redirect_to_login(_('You need to be signed in to view this page'))
734 else:
720 else:
735 raise HTTPForbidden()
721 raise HTTPForbidden()
736
722
737 def check_permissions(self, user):
723 def check_permissions(self, user):
738 raise NotImplementedError()
724 raise NotImplementedError()
739
725
740
726
741 class HasPermissionAnyDecorator(_PermsDecorator):
727 class HasPermissionAnyDecorator(_PermsDecorator):
742 """
728 """
743 Checks the user has any of the given global permissions.
729 Checks the user has any of the given global permissions.
744 """
730 """
745
731
746 def check_permissions(self, user):
732 def check_permissions(self, user):
747 global_permissions = user.permissions['global'] # usually very short
733 global_permissions = user.permissions['global'] # usually very short
748 return any(p in global_permissions for p in self.required_perms)
734 return any(p in global_permissions for p in self.required_perms)
749
735
750
736
751 class _PermDecorator(_PermsDecorator):
737 class _PermDecorator(_PermsDecorator):
752 """Base class for controller decorators with a single permission"""
738 """Base class for controller decorators with a single permission"""
753
739
754 def __init__(self, required_perm):
740 def __init__(self, required_perm):
755 _PermsDecorator.__init__(self, [required_perm])
741 _PermsDecorator.__init__(self, [required_perm])
756 self.required_perm = required_perm
742 self.required_perm = required_perm
757
743
758
744
759 class HasRepoPermissionLevelDecorator(_PermDecorator):
745 class HasRepoPermissionLevelDecorator(_PermDecorator):
760 """
746 """
761 Checks the user has at least the specified permission level for the requested repository.
747 Checks the user has at least the specified permission level for the requested repository.
762 """
748 """
763
749
764 def check_permissions(self, user):
750 def check_permissions(self, user):
765 repo_name = get_repo_slug(request)
751 repo_name = get_repo_slug(request)
766 return user.has_repository_permission_level(repo_name, self.required_perm)
752 return user.has_repository_permission_level(repo_name, self.required_perm)
767
753
768
754
769 class HasRepoGroupPermissionLevelDecorator(_PermDecorator):
755 class HasRepoGroupPermissionLevelDecorator(_PermDecorator):
770 """
756 """
771 Checks the user has any of given permissions for the requested repository group.
757 Checks the user has any of given permissions for the requested repository group.
772 """
758 """
773
759
774 def check_permissions(self, user):
760 def check_permissions(self, user):
775 repo_group_name = get_repo_group_slug(request)
761 repo_group_name = get_repo_group_slug(request)
776 return user.has_repository_group_permission_level(repo_group_name, self.required_perm)
762 return user.has_repository_group_permission_level(repo_group_name, self.required_perm)
777
763
778
764
779 class HasUserGroupPermissionLevelDecorator(_PermDecorator):
765 class HasUserGroupPermissionLevelDecorator(_PermDecorator):
780 """
766 """
781 Checks for access permission for any of given predicates for specific
767 Checks for access permission for any of given predicates for specific
782 user group. In order to fulfill the request any of predicates must be meet
768 user group. In order to fulfill the request any of predicates must be meet
783 """
769 """
784
770
785 def check_permissions(self, user):
771 def check_permissions(self, user):
786 user_group_name = get_user_group_slug(request)
772 user_group_name = get_user_group_slug(request)
787 return user.has_user_group_permission_level(user_group_name, self.required_perm)
773 return user.has_user_group_permission_level(user_group_name, self.required_perm)
788
774
789
775
790 #==============================================================================
776 #==============================================================================
791 # CHECK FUNCTIONS
777 # CHECK FUNCTIONS
792 #==============================================================================
778 #==============================================================================
793
779
794 class _PermsFunction(object):
780 class _PermsFunction(object):
795 """Base function for other check functions with multiple permissions"""
781 """Base function for other check functions with multiple permissions"""
796
782
797 def __init__(self, *required_perms):
783 def __init__(self, *required_perms):
798 self.required_perms = required_perms # usually very short - a list is thus fine
784 self.required_perms = required_perms # usually very short - a list is thus fine
799
785
800 def __nonzero__(self):
786 def __nonzero__(self):
801 """ Defend against accidentally forgetting to call the object
787 """ Defend against accidentally forgetting to call the object
802 and instead evaluating it directly in a boolean context,
788 and instead evaluating it directly in a boolean context,
803 which could have security implications.
789 which could have security implications.
804 """
790 """
805 raise AssertionError(self.__class__.__name__ + ' is not a bool and must be called!')
791 raise AssertionError(self.__class__.__name__ + ' is not a bool and must be called!')
806
792
807 def __call__(self, *a, **b):
793 def __call__(self, *a, **b):
808 raise NotImplementedError()
794 raise NotImplementedError()
809
795
810
796
811 class HasPermissionAny(_PermsFunction):
797 class HasPermissionAny(_PermsFunction):
812
798
813 def __call__(self, purpose=None):
799 def __call__(self, purpose=None):
814 global_permissions = request.authuser.permissions['global'] # usually very short
800 global_permissions = request.authuser.permissions['global'] # usually very short
815 ok = any(p in global_permissions for p in self.required_perms)
801 ok = any(p in global_permissions for p in self.required_perms)
816
802
817 log.debug('Check %s for global %s (%s): %s',
803 log.debug('Check %s for global %s (%s): %s',
818 request.authuser.username, self.required_perms, purpose, ok)
804 request.authuser.username, self.required_perms, purpose, ok)
819 return ok
805 return ok
820
806
821
807
822 class _PermFunction(_PermsFunction):
808 class _PermFunction(_PermsFunction):
823 """Base function for other check functions with a single permission"""
809 """Base function for other check functions with a single permission"""
824
810
825 def __init__(self, required_perm):
811 def __init__(self, required_perm):
826 _PermsFunction.__init__(self, [required_perm])
812 _PermsFunction.__init__(self, [required_perm])
827 self.required_perm = required_perm
813 self.required_perm = required_perm
828
814
829
815
830 class HasRepoPermissionLevel(_PermFunction):
816 class HasRepoPermissionLevel(_PermFunction):
831
817
832 def __call__(self, repo_name, purpose=None):
818 def __call__(self, repo_name, purpose=None):
833 return request.authuser.has_repository_permission_level(repo_name, self.required_perm, purpose)
819 return request.authuser.has_repository_permission_level(repo_name, self.required_perm, purpose)
834
820
835
821
836 class HasRepoGroupPermissionLevel(_PermFunction):
822 class HasRepoGroupPermissionLevel(_PermFunction):
837
823
838 def __call__(self, group_name, purpose=None):
824 def __call__(self, group_name, purpose=None):
839 return request.authuser.has_repository_group_permission_level(group_name, self.required_perm, purpose)
825 return request.authuser.has_repository_group_permission_level(group_name, self.required_perm, purpose)
840
826
841
827
842 class HasUserGroupPermissionLevel(_PermFunction):
828 class HasUserGroupPermissionLevel(_PermFunction):
843
829
844 def __call__(self, user_group_name, purpose=None):
830 def __call__(self, user_group_name, purpose=None):
845 return request.authuser.has_user_group_permission_level(user_group_name, self.required_perm, purpose)
831 return request.authuser.has_user_group_permission_level(user_group_name, self.required_perm, purpose)
846
832
847
833
848 #==============================================================================
834 #==============================================================================
849 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
835 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
850 #==============================================================================
836 #==============================================================================
851
837
852 class HasPermissionAnyMiddleware(object):
838 class HasPermissionAnyMiddleware(object):
853 def __init__(self, *perms):
839 def __init__(self, *perms):
854 self.required_perms = set(perms)
840 self.required_perms = set(perms)
855
841
856 def __call__(self, authuser, repo_name, purpose=None):
842 def __call__(self, authuser, repo_name, purpose=None):
857 # repo_name MUST be unicode, since we handle keys in ok
843 # repo_name MUST be unicode, since we handle keys in ok
858 # dict by unicode
844 # dict by unicode
859 repo_name = safe_unicode(repo_name)
845 repo_name = safe_unicode(repo_name)
860
846
861 try:
847 try:
862 ok = authuser.permissions['repositories'][repo_name] in self.required_perms
848 ok = authuser.permissions['repositories'][repo_name] in self.required_perms
863 except KeyError:
849 except KeyError:
864 ok = False
850 ok = False
865
851
866 log.debug('Middleware check %s for %s for repo %s (%s): %s', authuser.username, self.required_perms, repo_name, purpose, ok)
852 log.debug('Middleware check %s for %s for repo %s (%s): %s', authuser.username, self.required_perms, repo_name, purpose, ok)
867 return ok
853 return ok
868
854
869
855
870 def check_ip_access(source_ip, allowed_ips=None):
856 def check_ip_access(source_ip, allowed_ips=None):
871 """
857 """
872 Checks if source_ip is a subnet of any of allowed_ips.
858 Checks if source_ip is a subnet of any of allowed_ips.
873
859
874 :param source_ip:
860 :param source_ip:
875 :param allowed_ips: list of allowed ips together with mask
861 :param allowed_ips: list of allowed ips together with mask
876 """
862 """
877 from kallithea.lib import ipaddr
863 from kallithea.lib import ipaddr
878 source_ip = source_ip.split('%', 1)[0]
864 source_ip = source_ip.split('%', 1)[0]
879 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
865 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
880 if isinstance(allowed_ips, (tuple, list, set)):
866 if isinstance(allowed_ips, (tuple, list, set)):
881 for ip in allowed_ips:
867 for ip in allowed_ips:
882 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
868 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
883 log.debug('IP %s is network %s',
869 log.debug('IP %s is network %s',
884 ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip))
870 ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip))
885 return True
871 return True
886 return False
872 return False
General Comments 0
You need to be logged in to leave comments. Login now