##// END OF EJS Templates
permissions: rename repository permissions to mention those are access permissions....
marcink -
r3985:95009b08 default
parent child Browse files
Show More
@@ -1,59 +1,59 b''
1 1 .. _keyboard-shortcuts:
2 2
3 3 Keyboard Shortcuts
4 4 ==================
5 5
6 6 |RCE| has a number of user interface shortcut keys.
7 7
8 8 System Wide Shortcuts
9 9 ---------------------
10 10
11 11 .. rst-class:: dl-horizontal
12 12
13 13 \--:kbd:`/`
14 14 Open the |repo| search box.
15 15
16 16 \--:kbd:`?`
17 17 Open the |RCE| keyboard shortcuts page.
18 18
19 19 \--:kbd:`gh`
20 20 Go to the home page.
21 21
22 22 \--:kbd:`gg`
23 23 Go to my private gists page.
24 24
25 25 \--:kbd:`gG`
26 26 Go to my public gists page.
27 27
28 28 \--:kbd:`nr`
29 29 Go to the create new repository page.
30 30
31 31 \--:kbd:`ng`
32 32 Go to the new gist page.
33 33
34 34 Repository Shortcuts
35 35 --------------------
36 36
37 37 .. rst-class:: dl-horizontal
38 38
39 39
40 40 \--:kbd:`gs`
41 41 Go to the |repo| summary page.
42 42
43 43 \--:kbd:`gc`
44 44 Go to the changelog page.
45 45
46 46 \--:kbd:`gf`
47 47 Go to the files page.
48 48
49 49 \--:kbd:`gF`
50 50 Go to the files page with file search activated.
51 51
52 52 \--:kbd:`gp`
53 53 Go to the pull requests page.
54 54
55 55 \--:kbd:`go`
56 56 Go to the repository settings page.
57 57
58 58 \--:kbd:`gO`
59 Go to the repository permissions settings.
59 Go to the repository access permissions settings.
@@ -1,77 +1,77 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.tests.utils import permission_update_data_generator
24 24
25 25
26 26 def route_path(name, params=None, **kwargs):
27 27 import urllib
28 28
29 29 base_url = {
30 30 'edit_repo_perms': '/{repo_name}/settings/permissions'
31 31 # update is the same url
32 32 }[name].format(**kwargs)
33 33
34 34 if params:
35 35 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
36 36 return base_url
37 37
38 38
39 39 @pytest.mark.usefixtures("app")
40 40 class TestRepoPermissionsView(object):
41 41
42 42 def test_edit_perms_view(self, user_util, autologin_user):
43 43 repo = user_util.create_repo()
44 44 self.app.get(
45 45 route_path('edit_repo_perms',
46 46 repo_name=repo.repo_name), status=200)
47 47
48 48 def test_update_permissions(self, csrf_token, user_util):
49 49 repo = user_util.create_repo()
50 50 repo_name = repo.repo_name
51 51 user = user_util.create_user()
52 52 user_id = user.user_id
53 53 username = user.username
54 54
55 55 # grant new
56 56 form_data = permission_update_data_generator(
57 57 csrf_token,
58 58 default='repository.write',
59 59 grant=[(user_id, 'repository.write', username, 'user')])
60 60
61 61 response = self.app.post(
62 62 route_path('edit_repo_perms',
63 63 repo_name=repo_name), form_data).follow()
64 64
65 assert 'Repository permissions updated' in response
65 assert 'Repository access permissions updated' in response
66 66
67 67 # revoke given
68 68 form_data = permission_update_data_generator(
69 69 csrf_token,
70 70 default='repository.read',
71 71 revoke=[(user_id, 'user')])
72 72
73 73 response = self.app.post(
74 74 route_path('edit_repo_perms',
75 75 repo_name=repo_name), form_data).follow()
76 76
77 assert 'Repository permissions updated' in response
77 assert 'Repository access permissions updated' in response
@@ -1,122 +1,122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 31 from rhodecode.model.forms import RepoPermsForm
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.model.permission import PermissionModel
34 34 from rhodecode.model.repo import RepoModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class RepoSettingsPermissionsView(RepoAppView):
40 40
41 41 def load_default_context(self):
42 42 c = self._get_local_tmpl_context()
43 43 return c
44 44
45 45 @LoginRequired()
46 46 @HasRepoPermissionAnyDecorator('repository.admin')
47 47 @view_config(
48 48 route_name='edit_repo_perms', request_method='GET',
49 49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 50 def edit_permissions(self):
51 51 _ = self.request.translate
52 52 c = self.load_default_context()
53 53 c.active = 'permissions'
54 54 if self.request.GET.get('branch_permissions'):
55 55 h.flash(_('Explicitly add user or user group with write+ '
56 56 'permission to modify their branch permissions.'),
57 57 category='notice')
58 58 return self._get_template_context(c)
59 59
60 60 @LoginRequired()
61 61 @HasRepoPermissionAnyDecorator('repository.admin')
62 62 @CSRFRequired()
63 63 @view_config(
64 64 route_name='edit_repo_perms', request_method='POST',
65 65 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
66 66 def edit_permissions_update(self):
67 67 _ = self.request.translate
68 68 c = self.load_default_context()
69 69 c.active = 'permissions'
70 70 data = self.request.POST
71 71 # store private flag outside of HTML to verify if we can modify
72 72 # default user permissions, prevents submission of FAKE post data
73 73 # into the form for private repos
74 74 data['repo_private'] = self.db_repo.private
75 75 form = RepoPermsForm(self.request.translate)().to_python(data)
76 76 changes = RepoModel().update_permissions(
77 77 self.db_repo_name, form['perm_additions'], form['perm_updates'],
78 78 form['perm_deletions'])
79 79
80 80 action_data = {
81 81 'added': changes['added'],
82 82 'updated': changes['updated'],
83 83 'deleted': changes['deleted'],
84 84 }
85 85 audit_logger.store_web(
86 86 'repo.edit.permissions', action_data=action_data,
87 87 user=self._rhodecode_user, repo=self.db_repo)
88 88
89 89 Session().commit()
90 h.flash(_('Repository permissions updated'), category='success')
90 h.flash(_('Repository access permissions updated'), category='success')
91 91
92 92 PermissionModel().flush_user_permission_caches(changes)
93 93
94 94 raise HTTPFound(
95 95 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
96 96
97 97 @LoginRequired()
98 98 @HasRepoPermissionAnyDecorator('repository.admin')
99 99 @CSRFRequired()
100 100 @view_config(
101 101 route_name='edit_repo_perms_set_private', request_method='POST',
102 102 renderer='json_ext')
103 103 def edit_permissions_set_private_repo(self):
104 104 _ = self.request.translate
105 105 self.load_default_context()
106 106
107 107 try:
108 108 RepoModel().update(
109 109 self.db_repo, **{'repo_private': True, 'repo_name': self.db_repo_name})
110 110 Session().commit()
111 111
112 112 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
113 113 category='success')
114 114 except Exception:
115 115 log.exception("Exception during update of repository")
116 116 h.flash(_('Error occurred during update of repository {}').format(
117 117 self.db_repo_name), category='error')
118 118
119 119 return {
120 120 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
121 121 'private': True
122 122 }
@@ -1,2352 +1,2352 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import inspect
28 28 import collections
29 29 import fnmatch
30 30 import hashlib
31 31 import itertools
32 32 import logging
33 33 import random
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38
39 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 40 from sqlalchemy.orm.exc import ObjectDeletedError
41 41 from sqlalchemy.orm import joinedload
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 import rhodecode
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import (
49 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 51 from rhodecode.lib import rc_cache
52 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 55 from rhodecode.lib.caching_query import FromCache
56 56
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default'
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin'
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin)):
370 370 self.perm_origin_stack.setdefault(key, []).append(
371 371 (perm, origin))
372 372 dict.__setitem__(self, key, perm)
373 373
374 374
375 375 class BranchPermOriginDict(PermOriginDict):
376 376 """
377 377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 378
379 379 >>> perms = BranchPermOriginDict()
380 380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 381 >>> perms['resource']
382 382 {'*pattern': 'read'}
383 383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 384 >>> perms['resource']
385 385 {'*pattern': 'write'}
386 386 >>> perms.perm_origin_stack
387 387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 388 """
389 389 def __setitem__(self, key, (pattern, perm, origin)):
390 390
391 391 self.perm_origin_stack.setdefault(key, {}) \
392 392 .setdefault(pattern, []).append((perm, origin))
393 393
394 394 if key in self:
395 395 self[key].__setitem__(pattern, perm)
396 396 else:
397 397 patterns = collections.OrderedDict()
398 398 patterns[pattern] = perm
399 399 dict.__setitem__(self, key, patterns)
400 400
401 401
402 402 class PermissionCalculator(object):
403 403
404 404 def __init__(
405 405 self, user_id, scope, user_is_admin,
406 406 user_inherit_default_permissions, explicit, algo,
407 407 calculate_super_admin_as_user=False):
408 408
409 409 self.user_id = user_id
410 410 self.user_is_admin = user_is_admin
411 411 self.inherit_default_permissions = user_inherit_default_permissions
412 412 self.explicit = explicit
413 413 self.algo = algo
414 414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 415
416 416 scope = scope or {}
417 417 self.scope_repo_id = scope.get('repo_id')
418 418 self.scope_repo_group_id = scope.get('repo_group_id')
419 419 self.scope_user_group_id = scope.get('user_group_id')
420 420
421 421 self.default_user_id = User.get_default_user(cache=True).user_id
422 422
423 423 self.permissions_repositories = PermOriginDict()
424 424 self.permissions_repository_groups = PermOriginDict()
425 425 self.permissions_user_groups = PermOriginDict()
426 426 self.permissions_repository_branches = BranchPermOriginDict()
427 427 self.permissions_global = set()
428 428
429 429 self.default_repo_perms = Permission.get_default_repo_perms(
430 430 self.default_user_id, self.scope_repo_id)
431 431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 432 self.default_user_id, self.scope_repo_group_id)
433 433 self.default_user_group_perms = \
434 434 Permission.get_default_user_group_perms(
435 435 self.default_user_id, self.scope_user_group_id)
436 436
437 437 # default branch perms
438 438 self.default_branch_repo_perms = \
439 439 Permission.get_default_repo_branch_perms(
440 440 self.default_user_id, self.scope_repo_id)
441 441
442 442 def calculate(self):
443 443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 444 return self._calculate_admin_permissions()
445 445
446 446 self._calculate_global_default_permissions()
447 447 self._calculate_global_permissions()
448 448 self._calculate_default_permissions()
449 449 self._calculate_repository_permissions()
450 450 self._calculate_repository_branch_permissions()
451 451 self._calculate_repository_group_permissions()
452 452 self._calculate_user_group_permissions()
453 453 return self._permission_structure()
454 454
455 455 def _calculate_admin_permissions(self):
456 456 """
457 457 admin user have all default rights for repositories
458 458 and groups set to admin
459 459 """
460 460 self.permissions_global.add('hg.admin')
461 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 462
463 463 # repositories
464 464 for perm in self.default_repo_perms:
465 465 r_k = perm.UserRepoToPerm.repository.repo_name
466 466 archived = perm.UserRepoToPerm.repository.archived
467 467 p = 'repository.admin'
468 468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
469 469 # special case for archived repositories, which we block still even for
470 470 # super admins
471 471 if archived:
472 472 p = 'repository.read'
473 473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
474 474
475 475 # repository groups
476 476 for perm in self.default_repo_groups_perms:
477 477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 478 p = 'group.admin'
479 479 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
480 480
481 481 # user groups
482 482 for perm in self.default_user_group_perms:
483 483 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
484 484 p = 'usergroup.admin'
485 485 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
486 486
487 487 # branch permissions
488 488 # since super-admin also can have custom rule permissions
489 489 # we *always* need to calculate those inherited from default, and also explicit
490 490 self._calculate_default_permissions_repository_branches(
491 491 user_inherit_object_permissions=False)
492 492 self._calculate_repository_branch_permissions()
493 493
494 494 return self._permission_structure()
495 495
496 496 def _calculate_global_default_permissions(self):
497 497 """
498 498 global permissions taken from the default user
499 499 """
500 500 default_global_perms = UserToPerm.query()\
501 501 .filter(UserToPerm.user_id == self.default_user_id)\
502 502 .options(joinedload(UserToPerm.permission))
503 503
504 504 for perm in default_global_perms:
505 505 self.permissions_global.add(perm.permission.permission_name)
506 506
507 507 if self.user_is_admin:
508 508 self.permissions_global.add('hg.admin')
509 509 self.permissions_global.add('hg.create.write_on_repogroup.true')
510 510
511 511 def _calculate_global_permissions(self):
512 512 """
513 513 Set global system permissions with user permissions or permissions
514 514 taken from the user groups of the current user.
515 515
516 516 The permissions include repo creating, repo group creating, forking
517 517 etc.
518 518 """
519 519
520 520 # now we read the defined permissions and overwrite what we have set
521 521 # before those can be configured from groups or users explicitly.
522 522
523 523 # In case we want to extend this list we should make sure
524 524 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 525 _configurable = frozenset([
526 526 'hg.fork.none', 'hg.fork.repository',
527 527 'hg.create.none', 'hg.create.repository',
528 528 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 529 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 530 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 531 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 532 ])
533 533
534 534 # USER GROUPS comes first user group global permissions
535 535 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 536 .options(joinedload(UserGroupToPerm.permission))\
537 537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 538 UserGroupMember.users_group_id))\
539 539 .filter(UserGroupMember.user_id == self.user_id)\
540 540 .order_by(UserGroupToPerm.users_group_id)\
541 541 .all()
542 542
543 543 # need to group here by groups since user can be in more than
544 544 # one group, so we get all groups
545 545 _explicit_grouped_perms = [
546 546 [x, list(y)] for x, y in
547 547 itertools.groupby(user_perms_from_users_groups,
548 548 lambda _x: _x.users_group)]
549 549
550 550 for gr, perms in _explicit_grouped_perms:
551 551 # since user can be in multiple groups iterate over them and
552 552 # select the lowest permissions first (more explicit)
553 553 # TODO(marcink): do this^^
554 554
555 555 # group doesn't inherit default permissions so we actually set them
556 556 if not gr.inherit_default_permissions:
557 557 # NEED TO IGNORE all previously set configurable permissions
558 558 # and replace them with explicitly set from this user
559 559 # group permissions
560 560 self.permissions_global = self.permissions_global.difference(
561 561 _configurable)
562 562 for perm in perms:
563 563 self.permissions_global.add(perm.permission.permission_name)
564 564
565 565 # user explicit global permissions
566 566 user_perms = Session().query(UserToPerm)\
567 567 .options(joinedload(UserToPerm.permission))\
568 568 .filter(UserToPerm.user_id == self.user_id).all()
569 569
570 570 if not self.inherit_default_permissions:
571 571 # NEED TO IGNORE all configurable permissions and
572 572 # replace them with explicitly set from this user permissions
573 573 self.permissions_global = self.permissions_global.difference(
574 574 _configurable)
575 575 for perm in user_perms:
576 576 self.permissions_global.add(perm.permission.permission_name)
577 577
578 578 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 579 for perm in self.default_repo_perms:
580 580 r_k = perm.UserRepoToPerm.repository.repo_name
581 581 archived = perm.UserRepoToPerm.repository.archived
582 582 p = perm.Permission.permission_name
583 583 o = PermOrigin.REPO_DEFAULT
584 584 self.permissions_repositories[r_k] = p, o
585 585
586 586 # if we decide this user isn't inheriting permissions from
587 587 # default user we set him to .none so only explicit
588 588 # permissions work
589 589 if not user_inherit_object_permissions:
590 590 p = 'repository.none'
591 591 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
592 592 self.permissions_repositories[r_k] = p, o
593 593
594 594 if perm.Repository.private and not (
595 595 perm.Repository.user_id == self.user_id):
596 596 # disable defaults for private repos,
597 597 p = 'repository.none'
598 598 o = PermOrigin.REPO_PRIVATE
599 599 self.permissions_repositories[r_k] = p, o
600 600
601 601 elif perm.Repository.user_id == self.user_id:
602 602 # set admin if owner
603 603 p = 'repository.admin'
604 604 o = PermOrigin.REPO_OWNER
605 605 self.permissions_repositories[r_k] = p, o
606 606
607 607 if self.user_is_admin:
608 608 p = 'repository.admin'
609 609 o = PermOrigin.SUPER_ADMIN
610 610 self.permissions_repositories[r_k] = p, o
611 611
612 612 # finally in case of archived repositories, we downgrade higher
613 613 # permissions to read
614 614 if archived:
615 615 current_perm = self.permissions_repositories[r_k]
616 616 if current_perm in ['repository.write', 'repository.admin']:
617 617 p = 'repository.read'
618 618 o = PermOrigin.ARCHIVED
619 619 self.permissions_repositories[r_k] = p, o
620 620
621 621 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 622 for perm in self.default_branch_repo_perms:
623 623
624 624 r_k = perm.UserRepoToPerm.repository.repo_name
625 625 p = perm.Permission.permission_name
626 626 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 627 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628 628
629 629 if not self.explicit:
630 630 cur_perm = self.permissions_repository_branches.get(r_k)
631 631 if cur_perm:
632 632 cur_perm = cur_perm[pattern]
633 633 cur_perm = cur_perm or 'branch.none'
634 634
635 635 p = self._choose_permission(p, cur_perm)
636 636
637 637 # NOTE(marcink): register all pattern/perm instances in this
638 638 # special dict that aggregates entries
639 639 self.permissions_repository_branches[r_k] = pattern, p, o
640 640
641 641 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
642 642 for perm in self.default_repo_groups_perms:
643 643 rg_k = perm.UserRepoGroupToPerm.group.group_name
644 644 p = perm.Permission.permission_name
645 645 o = PermOrigin.REPOGROUP_DEFAULT
646 646 self.permissions_repository_groups[rg_k] = p, o
647 647
648 648 # if we decide this user isn't inheriting permissions from default
649 649 # user we set him to .none so only explicit permissions work
650 650 if not user_inherit_object_permissions:
651 651 p = 'group.none'
652 652 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
653 653 self.permissions_repository_groups[rg_k] = p, o
654 654
655 655 if perm.RepoGroup.user_id == self.user_id:
656 656 # set admin if owner
657 657 p = 'group.admin'
658 658 o = PermOrigin.REPOGROUP_OWNER
659 659 self.permissions_repository_groups[rg_k] = p, o
660 660
661 661 if self.user_is_admin:
662 662 p = 'group.admin'
663 663 o = PermOrigin.SUPER_ADMIN
664 664 self.permissions_repository_groups[rg_k] = p, o
665 665
666 666 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
667 667 for perm in self.default_user_group_perms:
668 668 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
669 669 p = perm.Permission.permission_name
670 670 o = PermOrigin.USERGROUP_DEFAULT
671 671 self.permissions_user_groups[u_k] = p, o
672 672
673 673 # if we decide this user isn't inheriting permissions from default
674 674 # user we set him to .none so only explicit permissions work
675 675 if not user_inherit_object_permissions:
676 676 p = 'usergroup.none'
677 677 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
678 678 self.permissions_user_groups[u_k] = p, o
679 679
680 680 if perm.UserGroup.user_id == self.user_id:
681 681 # set admin if owner
682 682 p = 'usergroup.admin'
683 683 o = PermOrigin.USERGROUP_OWNER
684 684 self.permissions_user_groups[u_k] = p, o
685 685
686 686 if self.user_is_admin:
687 687 p = 'usergroup.admin'
688 688 o = PermOrigin.SUPER_ADMIN
689 689 self.permissions_user_groups[u_k] = p, o
690 690
691 691 def _calculate_default_permissions(self):
692 692 """
693 693 Set default user permissions for repositories, repository branches,
694 694 repository groups, user groups taken from the default user.
695 695
696 696 Calculate inheritance of object permissions based on what we have now
697 697 in GLOBAL permissions. We check if .false is in GLOBAL since this is
698 698 explicitly set. Inherit is the opposite of .false being there.
699 699
700 700 .. note::
701 701
702 702 the syntax is little bit odd but what we need to check here is
703 703 the opposite of .false permission being in the list so even for
704 704 inconsistent state when both .true/.false is there
705 705 .false is more important
706 706
707 707 """
708 708 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
709 709 in self.permissions_global)
710 710
711 711 # default permissions inherited from `default` user permissions
712 712 self._calculate_default_permissions_repositories(
713 713 user_inherit_object_permissions)
714 714
715 715 self._calculate_default_permissions_repository_branches(
716 716 user_inherit_object_permissions)
717 717
718 718 self._calculate_default_permissions_repository_groups(
719 719 user_inherit_object_permissions)
720 720
721 721 self._calculate_default_permissions_user_groups(
722 722 user_inherit_object_permissions)
723 723
724 724 def _calculate_repository_permissions(self):
725 725 """
726 Repository permissions for the current user.
726 Repository access permissions for the current user.
727 727
728 728 Check if the user is part of user groups for this repository and
729 729 fill in the permission from it. `_choose_permission` decides of which
730 730 permission should be selected based on selected method.
731 731 """
732 732
733 733 # user group for repositories permissions
734 734 user_repo_perms_from_user_group = Permission\
735 735 .get_default_repo_perms_from_user_group(
736 736 self.user_id, self.scope_repo_id)
737 737
738 738 multiple_counter = collections.defaultdict(int)
739 739 for perm in user_repo_perms_from_user_group:
740 740 r_k = perm.UserGroupRepoToPerm.repository.repo_name
741 741 multiple_counter[r_k] += 1
742 742 p = perm.Permission.permission_name
743 743 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
744 744 .users_group.users_group_name
745 745
746 746 if multiple_counter[r_k] > 1:
747 747 cur_perm = self.permissions_repositories[r_k]
748 748 p = self._choose_permission(p, cur_perm)
749 749
750 750 self.permissions_repositories[r_k] = p, o
751 751
752 752 if perm.Repository.user_id == self.user_id:
753 753 # set admin if owner
754 754 p = 'repository.admin'
755 755 o = PermOrigin.REPO_OWNER
756 756 self.permissions_repositories[r_k] = p, o
757 757
758 758 if self.user_is_admin:
759 759 p = 'repository.admin'
760 760 o = PermOrigin.SUPER_ADMIN
761 761 self.permissions_repositories[r_k] = p, o
762 762
763 763 # user explicit permissions for repositories, overrides any specified
764 764 # by the group permission
765 765 user_repo_perms = Permission.get_default_repo_perms(
766 766 self.user_id, self.scope_repo_id)
767 767 for perm in user_repo_perms:
768 768 r_k = perm.UserRepoToPerm.repository.repo_name
769 769 p = perm.Permission.permission_name
770 770 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
771 771
772 772 if not self.explicit:
773 773 cur_perm = self.permissions_repositories.get(
774 774 r_k, 'repository.none')
775 775 p = self._choose_permission(p, cur_perm)
776 776
777 777 self.permissions_repositories[r_k] = p, o
778 778
779 779 if perm.Repository.user_id == self.user_id:
780 780 # set admin if owner
781 781 p = 'repository.admin'
782 782 o = PermOrigin.REPO_OWNER
783 783 self.permissions_repositories[r_k] = p, o
784 784
785 785 if self.user_is_admin:
786 786 p = 'repository.admin'
787 787 o = PermOrigin.SUPER_ADMIN
788 788 self.permissions_repositories[r_k] = p, o
789 789
790 790 def _calculate_repository_branch_permissions(self):
791 791 # user group for repositories permissions
792 792 user_repo_branch_perms_from_user_group = Permission\
793 793 .get_default_repo_branch_perms_from_user_group(
794 794 self.user_id, self.scope_repo_id)
795 795
796 796 multiple_counter = collections.defaultdict(int)
797 797 for perm in user_repo_branch_perms_from_user_group:
798 798 r_k = perm.UserGroupRepoToPerm.repository.repo_name
799 799 p = perm.Permission.permission_name
800 800 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
801 801 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
802 802 .users_group.users_group_name
803 803
804 804 multiple_counter[r_k] += 1
805 805 if multiple_counter[r_k] > 1:
806 806 cur_perm = self.permissions_repository_branches[r_k][pattern]
807 807 p = self._choose_permission(p, cur_perm)
808 808
809 809 self.permissions_repository_branches[r_k] = pattern, p, o
810 810
811 811 # user explicit branch permissions for repositories, overrides
812 812 # any specified by the group permission
813 813 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
814 814 self.user_id, self.scope_repo_id)
815 815
816 816 for perm in user_repo_branch_perms:
817 817
818 818 r_k = perm.UserRepoToPerm.repository.repo_name
819 819 p = perm.Permission.permission_name
820 820 pattern = perm.UserToRepoBranchPermission.branch_pattern
821 821 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
822 822
823 823 if not self.explicit:
824 824 cur_perm = self.permissions_repository_branches.get(r_k)
825 825 if cur_perm:
826 826 cur_perm = cur_perm[pattern]
827 827 cur_perm = cur_perm or 'branch.none'
828 828 p = self._choose_permission(p, cur_perm)
829 829
830 830 # NOTE(marcink): register all pattern/perm instances in this
831 831 # special dict that aggregates entries
832 832 self.permissions_repository_branches[r_k] = pattern, p, o
833 833
834 834 def _calculate_repository_group_permissions(self):
835 835 """
836 836 Repository group permissions for the current user.
837 837
838 838 Check if the user is part of user groups for repository groups and
839 839 fill in the permissions from it. `_choose_permission` decides of which
840 840 permission should be selected based on selected method.
841 841 """
842 842 # user group for repo groups permissions
843 843 user_repo_group_perms_from_user_group = Permission\
844 844 .get_default_group_perms_from_user_group(
845 845 self.user_id, self.scope_repo_group_id)
846 846
847 847 multiple_counter = collections.defaultdict(int)
848 848 for perm in user_repo_group_perms_from_user_group:
849 849 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
850 850 multiple_counter[rg_k] += 1
851 851 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
852 852 .users_group.users_group_name
853 853 p = perm.Permission.permission_name
854 854
855 855 if multiple_counter[rg_k] > 1:
856 856 cur_perm = self.permissions_repository_groups[rg_k]
857 857 p = self._choose_permission(p, cur_perm)
858 858 self.permissions_repository_groups[rg_k] = p, o
859 859
860 860 if perm.RepoGroup.user_id == self.user_id:
861 861 # set admin if owner, even for member of other user group
862 862 p = 'group.admin'
863 863 o = PermOrigin.REPOGROUP_OWNER
864 864 self.permissions_repository_groups[rg_k] = p, o
865 865
866 866 if self.user_is_admin:
867 867 p = 'group.admin'
868 868 o = PermOrigin.SUPER_ADMIN
869 869 self.permissions_repository_groups[rg_k] = p, o
870 870
871 871 # user explicit permissions for repository groups
872 872 user_repo_groups_perms = Permission.get_default_group_perms(
873 873 self.user_id, self.scope_repo_group_id)
874 874 for perm in user_repo_groups_perms:
875 875 rg_k = perm.UserRepoGroupToPerm.group.group_name
876 876 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
877 877 .user.username
878 878 p = perm.Permission.permission_name
879 879
880 880 if not self.explicit:
881 881 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
882 882 p = self._choose_permission(p, cur_perm)
883 883
884 884 self.permissions_repository_groups[rg_k] = p, o
885 885
886 886 if perm.RepoGroup.user_id == self.user_id:
887 887 # set admin if owner
888 888 p = 'group.admin'
889 889 o = PermOrigin.REPOGROUP_OWNER
890 890 self.permissions_repository_groups[rg_k] = p, o
891 891
892 892 if self.user_is_admin:
893 893 p = 'group.admin'
894 894 o = PermOrigin.SUPER_ADMIN
895 895 self.permissions_repository_groups[rg_k] = p, o
896 896
897 897 def _calculate_user_group_permissions(self):
898 898 """
899 899 User group permissions for the current user.
900 900 """
901 901 # user group for user group permissions
902 902 user_group_from_user_group = Permission\
903 903 .get_default_user_group_perms_from_user_group(
904 904 self.user_id, self.scope_user_group_id)
905 905
906 906 multiple_counter = collections.defaultdict(int)
907 907 for perm in user_group_from_user_group:
908 908 ug_k = perm.UserGroupUserGroupToPerm\
909 909 .target_user_group.users_group_name
910 910 multiple_counter[ug_k] += 1
911 911 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
912 912 .user_group.users_group_name
913 913 p = perm.Permission.permission_name
914 914
915 915 if multiple_counter[ug_k] > 1:
916 916 cur_perm = self.permissions_user_groups[ug_k]
917 917 p = self._choose_permission(p, cur_perm)
918 918
919 919 self.permissions_user_groups[ug_k] = p, o
920 920
921 921 if perm.UserGroup.user_id == self.user_id:
922 922 # set admin if owner, even for member of other user group
923 923 p = 'usergroup.admin'
924 924 o = PermOrigin.USERGROUP_OWNER
925 925 self.permissions_user_groups[ug_k] = p, o
926 926
927 927 if self.user_is_admin:
928 928 p = 'usergroup.admin'
929 929 o = PermOrigin.SUPER_ADMIN
930 930 self.permissions_user_groups[ug_k] = p, o
931 931
932 932 # user explicit permission for user groups
933 933 user_user_groups_perms = Permission.get_default_user_group_perms(
934 934 self.user_id, self.scope_user_group_id)
935 935 for perm in user_user_groups_perms:
936 936 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
937 937 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
938 938 .user.username
939 939 p = perm.Permission.permission_name
940 940
941 941 if not self.explicit:
942 942 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
943 943 p = self._choose_permission(p, cur_perm)
944 944
945 945 self.permissions_user_groups[ug_k] = p, o
946 946
947 947 if perm.UserGroup.user_id == self.user_id:
948 948 # set admin if owner
949 949 p = 'usergroup.admin'
950 950 o = PermOrigin.USERGROUP_OWNER
951 951 self.permissions_user_groups[ug_k] = p, o
952 952
953 953 if self.user_is_admin:
954 954 p = 'usergroup.admin'
955 955 o = PermOrigin.SUPER_ADMIN
956 956 self.permissions_user_groups[ug_k] = p, o
957 957
958 958 def _choose_permission(self, new_perm, cur_perm):
959 959 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
960 960 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
961 961 if self.algo == 'higherwin':
962 962 if new_perm_val > cur_perm_val:
963 963 return new_perm
964 964 return cur_perm
965 965 elif self.algo == 'lowerwin':
966 966 if new_perm_val < cur_perm_val:
967 967 return new_perm
968 968 return cur_perm
969 969
970 970 def _permission_structure(self):
971 971 return {
972 972 'global': self.permissions_global,
973 973 'repositories': self.permissions_repositories,
974 974 'repository_branches': self.permissions_repository_branches,
975 975 'repositories_groups': self.permissions_repository_groups,
976 976 'user_groups': self.permissions_user_groups,
977 977 }
978 978
979 979
980 980 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
981 981 """
982 982 Check if given controller_name is in whitelist of auth token access
983 983 """
984 984 if not whitelist:
985 985 from rhodecode import CONFIG
986 986 whitelist = aslist(
987 987 CONFIG.get('api_access_controllers_whitelist'), sep=',')
988 988 # backward compat translation
989 989 compat = {
990 990 # old controller, new VIEW
991 991 'ChangesetController:*': 'RepoCommitsView:*',
992 992 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
993 993 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
994 994 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
995 995 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
996 996 'GistsController:*': 'GistView:*',
997 997 }
998 998
999 999 log.debug(
1000 1000 'Allowed views for AUTH TOKEN access: %s', whitelist)
1001 1001 auth_token_access_valid = False
1002 1002
1003 1003 for entry in whitelist:
1004 1004 token_match = True
1005 1005 if entry in compat:
1006 1006 # translate from old Controllers to Pyramid Views
1007 1007 entry = compat[entry]
1008 1008
1009 1009 if '@' in entry:
1010 1010 # specific AuthToken
1011 1011 entry, allowed_token = entry.split('@', 1)
1012 1012 token_match = auth_token == allowed_token
1013 1013
1014 1014 if fnmatch.fnmatch(view_name, entry) and token_match:
1015 1015 auth_token_access_valid = True
1016 1016 break
1017 1017
1018 1018 if auth_token_access_valid:
1019 1019 log.debug('view: `%s` matches entry in whitelist: %s',
1020 1020 view_name, whitelist)
1021 1021
1022 1022 else:
1023 1023 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1024 1024 % (view_name, whitelist))
1025 1025 if auth_token:
1026 1026 # if we use auth token key and don't have access it's a warning
1027 1027 log.warning(msg)
1028 1028 else:
1029 1029 log.debug(msg)
1030 1030
1031 1031 return auth_token_access_valid
1032 1032
1033 1033
1034 1034 class AuthUser(object):
1035 1035 """
1036 1036 A simple object that handles all attributes of user in RhodeCode
1037 1037
1038 1038 It does lookup based on API key,given user, or user present in session
1039 1039 Then it fills all required information for such user. It also checks if
1040 1040 anonymous access is enabled and if so, it returns default user as logged in
1041 1041 """
1042 1042 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1043 1043
1044 1044 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1045 1045
1046 1046 self.user_id = user_id
1047 1047 self._api_key = api_key
1048 1048
1049 1049 self.api_key = None
1050 1050 self.username = username
1051 1051 self.ip_addr = ip_addr
1052 1052 self.name = ''
1053 1053 self.lastname = ''
1054 1054 self.first_name = ''
1055 1055 self.last_name = ''
1056 1056 self.email = ''
1057 1057 self.is_authenticated = False
1058 1058 self.admin = False
1059 1059 self.inherit_default_permissions = False
1060 1060 self.password = ''
1061 1061
1062 1062 self.anonymous_user = None # propagated on propagate_data
1063 1063 self.propagate_data()
1064 1064 self._instance = None
1065 1065 self._permissions_scoped_cache = {} # used to bind scoped calculation
1066 1066
1067 1067 @LazyProperty
1068 1068 def permissions(self):
1069 1069 return self.get_perms(user=self, cache=None)
1070 1070
1071 1071 @LazyProperty
1072 1072 def permissions_safe(self):
1073 1073 """
1074 1074 Filtered permissions excluding not allowed repositories
1075 1075 """
1076 1076 perms = self.get_perms(user=self, cache=None)
1077 1077
1078 1078 perms['repositories'] = {
1079 1079 k: v for k, v in perms['repositories'].items()
1080 1080 if v != 'repository.none'}
1081 1081 perms['repositories_groups'] = {
1082 1082 k: v for k, v in perms['repositories_groups'].items()
1083 1083 if v != 'group.none'}
1084 1084 perms['user_groups'] = {
1085 1085 k: v for k, v in perms['user_groups'].items()
1086 1086 if v != 'usergroup.none'}
1087 1087 perms['repository_branches'] = {
1088 1088 k: v for k, v in perms['repository_branches'].iteritems()
1089 1089 if v != 'branch.none'}
1090 1090 return perms
1091 1091
1092 1092 @LazyProperty
1093 1093 def permissions_full_details(self):
1094 1094 return self.get_perms(
1095 1095 user=self, cache=None, calculate_super_admin=True)
1096 1096
1097 1097 def permissions_with_scope(self, scope):
1098 1098 """
1099 1099 Call the get_perms function with scoped data. The scope in that function
1100 1100 narrows the SQL calls to the given ID of objects resulting in fetching
1101 1101 Just particular permission we want to obtain. If scope is an empty dict
1102 1102 then it basically narrows the scope to GLOBAL permissions only.
1103 1103
1104 1104 :param scope: dict
1105 1105 """
1106 1106 if 'repo_name' in scope:
1107 1107 obj = Repository.get_by_repo_name(scope['repo_name'])
1108 1108 if obj:
1109 1109 scope['repo_id'] = obj.repo_id
1110 1110 _scope = collections.OrderedDict()
1111 1111 _scope['repo_id'] = -1
1112 1112 _scope['user_group_id'] = -1
1113 1113 _scope['repo_group_id'] = -1
1114 1114
1115 1115 for k in sorted(scope.keys()):
1116 1116 _scope[k] = scope[k]
1117 1117
1118 1118 # store in cache to mimic how the @LazyProperty works,
1119 1119 # the difference here is that we use the unique key calculated
1120 1120 # from params and values
1121 1121 return self.get_perms(user=self, cache=None, scope=_scope)
1122 1122
1123 1123 def get_instance(self):
1124 1124 return User.get(self.user_id)
1125 1125
1126 1126 def propagate_data(self):
1127 1127 """
1128 1128 Fills in user data and propagates values to this instance. Maps fetched
1129 1129 user attributes to this class instance attributes
1130 1130 """
1131 1131 log.debug('AuthUser: starting data propagation for new potential user')
1132 1132 user_model = UserModel()
1133 1133 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1134 1134 is_user_loaded = False
1135 1135
1136 1136 # lookup by userid
1137 1137 if self.user_id is not None and self.user_id != anon_user.user_id:
1138 1138 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1139 1139 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1140 1140
1141 1141 # try go get user by api key
1142 1142 elif self._api_key and self._api_key != anon_user.api_key:
1143 1143 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1144 1144 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1145 1145
1146 1146 # lookup by username
1147 1147 elif self.username:
1148 1148 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1149 1149 is_user_loaded = user_model.fill_data(self, username=self.username)
1150 1150 else:
1151 1151 log.debug('No data in %s that could been used to log in', self)
1152 1152
1153 1153 if not is_user_loaded:
1154 1154 log.debug(
1155 1155 'Failed to load user. Fallback to default user %s', anon_user)
1156 1156 # if we cannot authenticate user try anonymous
1157 1157 if anon_user.active:
1158 1158 log.debug('default user is active, using it as a session user')
1159 1159 user_model.fill_data(self, user_id=anon_user.user_id)
1160 1160 # then we set this user is logged in
1161 1161 self.is_authenticated = True
1162 1162 else:
1163 1163 log.debug('default user is NOT active')
1164 1164 # in case of disabled anonymous user we reset some of the
1165 1165 # parameters so such user is "corrupted", skipping the fill_data
1166 1166 for attr in ['user_id', 'username', 'admin', 'active']:
1167 1167 setattr(self, attr, None)
1168 1168 self.is_authenticated = False
1169 1169
1170 1170 if not self.username:
1171 1171 self.username = 'None'
1172 1172
1173 1173 log.debug('AuthUser: propagated user is now %s', self)
1174 1174
1175 1175 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1176 1176 calculate_super_admin=False, cache=None):
1177 1177 """
1178 1178 Fills user permission attribute with permissions taken from database
1179 1179 works for permissions given for repositories, and for permissions that
1180 1180 are granted to groups
1181 1181
1182 1182 :param user: instance of User object from database
1183 1183 :param explicit: In case there are permissions both for user and a group
1184 1184 that user is part of, explicit flag will defiine if user will
1185 1185 explicitly override permissions from group, if it's False it will
1186 1186 make decision based on the algo
1187 1187 :param algo: algorithm to decide what permission should be choose if
1188 1188 it's multiple defined, eg user in two different groups. It also
1189 1189 decides if explicit flag is turned off how to specify the permission
1190 1190 for case when user is in a group + have defined separate permission
1191 1191 :param calculate_super_admin: calculate permissions for super-admin in the
1192 1192 same way as for regular user without speedups
1193 1193 :param cache: Use caching for calculation, None = let the cache backend decide
1194 1194 """
1195 1195 user_id = user.user_id
1196 1196 user_is_admin = user.is_admin
1197 1197
1198 1198 # inheritance of global permissions like create repo/fork repo etc
1199 1199 user_inherit_default_permissions = user.inherit_default_permissions
1200 1200
1201 1201 cache_seconds = safe_int(
1202 1202 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1203 1203
1204 1204 if cache is None:
1205 1205 # let the backend cache decide
1206 1206 cache_on = cache_seconds > 0
1207 1207 else:
1208 1208 cache_on = cache
1209 1209
1210 1210 log.debug(
1211 1211 'Computing PERMISSION tree for user %s scope `%s` '
1212 1212 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1213 1213
1214 1214 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1215 1215 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1216 1216
1217 1217 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1218 1218 condition=cache_on)
1219 1219 def compute_perm_tree(cache_name,
1220 1220 user_id, scope, user_is_admin,user_inherit_default_permissions,
1221 1221 explicit, algo, calculate_super_admin):
1222 1222 return _cached_perms_data(
1223 1223 user_id, scope, user_is_admin, user_inherit_default_permissions,
1224 1224 explicit, algo, calculate_super_admin)
1225 1225
1226 1226 start = time.time()
1227 1227 result = compute_perm_tree(
1228 1228 'permissions', user_id, scope, user_is_admin,
1229 1229 user_inherit_default_permissions, explicit, algo,
1230 1230 calculate_super_admin)
1231 1231
1232 1232 result_repr = []
1233 1233 for k in result:
1234 1234 result_repr.append((k, len(result[k])))
1235 1235 total = time.time() - start
1236 1236 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1237 1237 user, total, result_repr)
1238 1238
1239 1239 return result
1240 1240
1241 1241 @property
1242 1242 def is_default(self):
1243 1243 return self.username == User.DEFAULT_USER
1244 1244
1245 1245 @property
1246 1246 def is_admin(self):
1247 1247 return self.admin
1248 1248
1249 1249 @property
1250 1250 def is_user_object(self):
1251 1251 return self.user_id is not None
1252 1252
1253 1253 @property
1254 1254 def repositories_admin(self):
1255 1255 """
1256 1256 Returns list of repositories you're an admin of
1257 1257 """
1258 1258 return [
1259 1259 x[0] for x in self.permissions['repositories'].items()
1260 1260 if x[1] == 'repository.admin']
1261 1261
1262 1262 @property
1263 1263 def repository_groups_admin(self):
1264 1264 """
1265 1265 Returns list of repository groups you're an admin of
1266 1266 """
1267 1267 return [
1268 1268 x[0] for x in self.permissions['repositories_groups'].items()
1269 1269 if x[1] == 'group.admin']
1270 1270
1271 1271 @property
1272 1272 def user_groups_admin(self):
1273 1273 """
1274 1274 Returns list of user groups you're an admin of
1275 1275 """
1276 1276 return [
1277 1277 x[0] for x in self.permissions['user_groups'].items()
1278 1278 if x[1] == 'usergroup.admin']
1279 1279
1280 1280 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1281 1281 """
1282 1282 Returns list of repository ids that user have access to based on given
1283 1283 perms. The cache flag should be only used in cases that are used for
1284 1284 display purposes, NOT IN ANY CASE for permission checks.
1285 1285 """
1286 1286 from rhodecode.model.scm import RepoList
1287 1287 if not perms:
1288 1288 perms = [
1289 1289 'repository.read', 'repository.write', 'repository.admin']
1290 1290
1291 1291 def _cached_repo_acl(user_id, perm_def, _name_filter):
1292 1292 qry = Repository.query()
1293 1293 if _name_filter:
1294 1294 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1295 1295 qry = qry.filter(
1296 1296 Repository.repo_name.ilike(ilike_expression))
1297 1297
1298 1298 return [x.repo_id for x in
1299 1299 RepoList(qry, perm_set=perm_def)]
1300 1300
1301 1301 return _cached_repo_acl(self.user_id, perms, name_filter)
1302 1302
1303 1303 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 1304 """
1305 1305 Returns list of repository group ids that user have access to based on given
1306 1306 perms. The cache flag should be only used in cases that are used for
1307 1307 display purposes, NOT IN ANY CASE for permission checks.
1308 1308 """
1309 1309 from rhodecode.model.scm import RepoGroupList
1310 1310 if not perms:
1311 1311 perms = [
1312 1312 'group.read', 'group.write', 'group.admin']
1313 1313
1314 1314 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1315 1315 qry = RepoGroup.query()
1316 1316 if _name_filter:
1317 1317 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 1318 qry = qry.filter(
1319 1319 RepoGroup.group_name.ilike(ilike_expression))
1320 1320
1321 1321 return [x.group_id for x in
1322 1322 RepoGroupList(qry, perm_set=perm_def)]
1323 1323
1324 1324 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1325 1325
1326 1326 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1327 1327 """
1328 1328 Returns list of user group ids that user have access to based on given
1329 1329 perms. The cache flag should be only used in cases that are used for
1330 1330 display purposes, NOT IN ANY CASE for permission checks.
1331 1331 """
1332 1332 from rhodecode.model.scm import UserGroupList
1333 1333 if not perms:
1334 1334 perms = [
1335 1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1336 1336
1337 1337 def _cached_user_group_acl(user_id, perm_def, name_filter):
1338 1338 qry = UserGroup.query()
1339 1339 if name_filter:
1340 1340 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1341 1341 qry = qry.filter(
1342 1342 UserGroup.users_group_name.ilike(ilike_expression))
1343 1343
1344 1344 return [x.users_group_id for x in
1345 1345 UserGroupList(qry, perm_set=perm_def)]
1346 1346
1347 1347 return _cached_user_group_acl(self.user_id, perms, name_filter)
1348 1348
1349 1349 @property
1350 1350 def ip_allowed(self):
1351 1351 """
1352 1352 Checks if ip_addr used in constructor is allowed from defined list of
1353 1353 allowed ip_addresses for user
1354 1354
1355 1355 :returns: boolean, True if ip is in allowed ip range
1356 1356 """
1357 1357 # check IP
1358 1358 inherit = self.inherit_default_permissions
1359 1359 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1360 1360 inherit_from_default=inherit)
1361 1361 @property
1362 1362 def personal_repo_group(self):
1363 1363 return RepoGroup.get_user_personal_repo_group(self.user_id)
1364 1364
1365 1365 @LazyProperty
1366 1366 def feed_token(self):
1367 1367 return self.get_instance().feed_token
1368 1368
1369 1369 @classmethod
1370 1370 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1371 1371 allowed_ips = AuthUser.get_allowed_ips(
1372 1372 user_id, cache=True, inherit_from_default=inherit_from_default)
1373 1373 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1374 1374 log.debug('IP:%s for user %s is in range of %s',
1375 1375 ip_addr, user_id, allowed_ips)
1376 1376 return True
1377 1377 else:
1378 1378 log.info('Access for IP:%s forbidden for user %s, '
1379 1379 'not in %s', ip_addr, user_id, allowed_ips)
1380 1380 return False
1381 1381
1382 1382 def get_branch_permissions(self, repo_name, perms=None):
1383 1383 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1384 1384 branch_perms = perms.get('repository_branches', {})
1385 1385 if not branch_perms:
1386 1386 return {}
1387 1387 repo_branch_perms = branch_perms.get(repo_name)
1388 1388 return repo_branch_perms or {}
1389 1389
1390 1390 def get_rule_and_branch_permission(self, repo_name, branch_name):
1391 1391 """
1392 1392 Check if this AuthUser has defined any permissions for branches. If any of
1393 1393 the rules match in order, we return the matching permissions
1394 1394 """
1395 1395
1396 1396 rule = default_perm = ''
1397 1397
1398 1398 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1399 1399 if not repo_branch_perms:
1400 1400 return rule, default_perm
1401 1401
1402 1402 # now calculate the permissions
1403 1403 for pattern, branch_perm in repo_branch_perms.items():
1404 1404 if fnmatch.fnmatch(branch_name, pattern):
1405 1405 rule = '`{}`=>{}'.format(pattern, branch_perm)
1406 1406 return rule, branch_perm
1407 1407
1408 1408 return rule, default_perm
1409 1409
1410 1410 def __repr__(self):
1411 1411 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1412 1412 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1413 1413
1414 1414 def set_authenticated(self, authenticated=True):
1415 1415 if self.user_id != self.anonymous_user.user_id:
1416 1416 self.is_authenticated = authenticated
1417 1417
1418 1418 def get_cookie_store(self):
1419 1419 return {
1420 1420 'username': self.username,
1421 1421 'password': md5(self.password or ''),
1422 1422 'user_id': self.user_id,
1423 1423 'is_authenticated': self.is_authenticated
1424 1424 }
1425 1425
1426 1426 @classmethod
1427 1427 def from_cookie_store(cls, cookie_store):
1428 1428 """
1429 1429 Creates AuthUser from a cookie store
1430 1430
1431 1431 :param cls:
1432 1432 :param cookie_store:
1433 1433 """
1434 1434 user_id = cookie_store.get('user_id')
1435 1435 username = cookie_store.get('username')
1436 1436 api_key = cookie_store.get('api_key')
1437 1437 return AuthUser(user_id, api_key, username)
1438 1438
1439 1439 @classmethod
1440 1440 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1441 1441 _set = set()
1442 1442
1443 1443 if inherit_from_default:
1444 1444 def_user_id = User.get_default_user(cache=True).user_id
1445 1445 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1446 1446 if cache:
1447 1447 default_ips = default_ips.options(
1448 1448 FromCache("sql_cache_short", "get_user_ips_default"))
1449 1449
1450 1450 # populate from default user
1451 1451 for ip in default_ips:
1452 1452 try:
1453 1453 _set.add(ip.ip_addr)
1454 1454 except ObjectDeletedError:
1455 1455 # since we use heavy caching sometimes it happens that
1456 1456 # we get deleted objects here, we just skip them
1457 1457 pass
1458 1458
1459 1459 # NOTE:(marcink) we don't want to load any rules for empty
1460 1460 # user_id which is the case of access of non logged users when anonymous
1461 1461 # access is disabled
1462 1462 user_ips = []
1463 1463 if user_id:
1464 1464 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1465 1465 if cache:
1466 1466 user_ips = user_ips.options(
1467 1467 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1468 1468
1469 1469 for ip in user_ips:
1470 1470 try:
1471 1471 _set.add(ip.ip_addr)
1472 1472 except ObjectDeletedError:
1473 1473 # since we use heavy caching sometimes it happens that we get
1474 1474 # deleted objects here, we just skip them
1475 1475 pass
1476 1476 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1477 1477
1478 1478
1479 1479 def set_available_permissions(settings):
1480 1480 """
1481 1481 This function will propagate pyramid settings with all available defined
1482 1482 permission given in db. We don't want to check each time from db for new
1483 1483 permissions since adding a new permission also requires application restart
1484 1484 ie. to decorate new views with the newly created permission
1485 1485
1486 1486 :param settings: current pyramid registry.settings
1487 1487
1488 1488 """
1489 1489 log.debug('auth: getting information about all available permissions')
1490 1490 try:
1491 1491 sa = meta.Session
1492 1492 all_perms = sa.query(Permission).all()
1493 1493 settings.setdefault('available_permissions',
1494 1494 [x.permission_name for x in all_perms])
1495 1495 log.debug('auth: set available permissions')
1496 1496 except Exception:
1497 1497 log.exception('Failed to fetch permissions from the database.')
1498 1498 raise
1499 1499
1500 1500
1501 1501 def get_csrf_token(session, force_new=False, save_if_missing=True):
1502 1502 """
1503 1503 Return the current authentication token, creating one if one doesn't
1504 1504 already exist and the save_if_missing flag is present.
1505 1505
1506 1506 :param session: pass in the pyramid session, else we use the global ones
1507 1507 :param force_new: force to re-generate the token and store it in session
1508 1508 :param save_if_missing: save the newly generated token if it's missing in
1509 1509 session
1510 1510 """
1511 1511 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1512 1512 # from pyramid.csrf import get_csrf_token
1513 1513
1514 1514 if (csrf_token_key not in session and save_if_missing) or force_new:
1515 1515 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1516 1516 session[csrf_token_key] = token
1517 1517 if hasattr(session, 'save'):
1518 1518 session.save()
1519 1519 return session.get(csrf_token_key)
1520 1520
1521 1521
1522 1522 def get_request(perm_class_instance):
1523 1523 from pyramid.threadlocal import get_current_request
1524 1524 pyramid_request = get_current_request()
1525 1525 return pyramid_request
1526 1526
1527 1527
1528 1528 # CHECK DECORATORS
1529 1529 class CSRFRequired(object):
1530 1530 """
1531 1531 Decorator for authenticating a form
1532 1532
1533 1533 This decorator uses an authorization token stored in the client's
1534 1534 session for prevention of certain Cross-site request forgery (CSRF)
1535 1535 attacks (See
1536 1536 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1537 1537 information).
1538 1538
1539 1539 For use with the ``webhelpers.secure_form`` helper functions.
1540 1540
1541 1541 """
1542 1542 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1543 1543 except_methods=None):
1544 1544 self.token = token
1545 1545 self.header = header
1546 1546 self.except_methods = except_methods or []
1547 1547
1548 1548 def __call__(self, func):
1549 1549 return get_cython_compat_decorator(self.__wrapper, func)
1550 1550
1551 1551 def _get_csrf(self, _request):
1552 1552 return _request.POST.get(self.token, _request.headers.get(self.header))
1553 1553
1554 1554 def check_csrf(self, _request, cur_token):
1555 1555 supplied_token = self._get_csrf(_request)
1556 1556 return supplied_token and supplied_token == cur_token
1557 1557
1558 1558 def _get_request(self):
1559 1559 return get_request(self)
1560 1560
1561 1561 def __wrapper(self, func, *fargs, **fkwargs):
1562 1562 request = self._get_request()
1563 1563
1564 1564 if request.method in self.except_methods:
1565 1565 return func(*fargs, **fkwargs)
1566 1566
1567 1567 cur_token = get_csrf_token(request.session, save_if_missing=False)
1568 1568 if self.check_csrf(request, cur_token):
1569 1569 if request.POST.get(self.token):
1570 1570 del request.POST[self.token]
1571 1571 return func(*fargs, **fkwargs)
1572 1572 else:
1573 1573 reason = 'token-missing'
1574 1574 supplied_token = self._get_csrf(request)
1575 1575 if supplied_token and cur_token != supplied_token:
1576 1576 reason = 'token-mismatch [%s:%s]' % (
1577 1577 cur_token or ''[:6], supplied_token or ''[:6])
1578 1578
1579 1579 csrf_message = \
1580 1580 ("Cross-site request forgery detected, request denied. See "
1581 1581 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1582 1582 "more information.")
1583 1583 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1584 1584 'REMOTE_ADDR:%s, HEADERS:%s' % (
1585 1585 request, reason, request.remote_addr, request.headers))
1586 1586
1587 1587 raise HTTPForbidden(explanation=csrf_message)
1588 1588
1589 1589
1590 1590 class LoginRequired(object):
1591 1591 """
1592 1592 Must be logged in to execute this function else
1593 1593 redirect to login page
1594 1594
1595 1595 :param api_access: if enabled this checks only for valid auth token
1596 1596 and grants access based on valid token
1597 1597 """
1598 1598 def __init__(self, auth_token_access=None):
1599 1599 self.auth_token_access = auth_token_access
1600 1600
1601 1601 def __call__(self, func):
1602 1602 return get_cython_compat_decorator(self.__wrapper, func)
1603 1603
1604 1604 def _get_request(self):
1605 1605 return get_request(self)
1606 1606
1607 1607 def __wrapper(self, func, *fargs, **fkwargs):
1608 1608 from rhodecode.lib import helpers as h
1609 1609 cls = fargs[0]
1610 1610 user = cls._rhodecode_user
1611 1611 request = self._get_request()
1612 1612 _ = request.translate
1613 1613
1614 1614 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1615 1615 log.debug('Starting login restriction checks for user: %s', user)
1616 1616 # check if our IP is allowed
1617 1617 ip_access_valid = True
1618 1618 if not user.ip_allowed:
1619 1619 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1620 1620 category='warning')
1621 1621 ip_access_valid = False
1622 1622
1623 1623 # check if we used an APIKEY and it's a valid one
1624 1624 # defined white-list of controllers which API access will be enabled
1625 1625 _auth_token = request.GET.get(
1626 1626 'auth_token', '') or request.GET.get('api_key', '')
1627 1627 auth_token_access_valid = allowed_auth_token_access(
1628 1628 loc, auth_token=_auth_token)
1629 1629
1630 1630 # explicit controller is enabled or API is in our whitelist
1631 1631 if self.auth_token_access or auth_token_access_valid:
1632 1632 log.debug('Checking AUTH TOKEN access for %s', cls)
1633 1633 db_user = user.get_instance()
1634 1634
1635 1635 if db_user:
1636 1636 if self.auth_token_access:
1637 1637 roles = self.auth_token_access
1638 1638 else:
1639 1639 roles = [UserApiKeys.ROLE_HTTP]
1640 1640 token_match = db_user.authenticate_by_token(
1641 1641 _auth_token, roles=roles)
1642 1642 else:
1643 1643 log.debug('Unable to fetch db instance for auth user: %s', user)
1644 1644 token_match = False
1645 1645
1646 1646 if _auth_token and token_match:
1647 1647 auth_token_access_valid = True
1648 1648 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1649 1649 else:
1650 1650 auth_token_access_valid = False
1651 1651 if not _auth_token:
1652 1652 log.debug("AUTH TOKEN *NOT* present in request")
1653 1653 else:
1654 1654 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1655 1655
1656 1656 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1657 1657 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1658 1658 else 'AUTH_TOKEN_AUTH'
1659 1659
1660 1660 if ip_access_valid and (
1661 1661 user.is_authenticated or auth_token_access_valid):
1662 1662 log.info('user %s authenticating with:%s IS authenticated on func %s',
1663 1663 user, reason, loc)
1664 1664
1665 1665 return func(*fargs, **fkwargs)
1666 1666 else:
1667 1667 log.warning(
1668 1668 'user %s authenticating with:%s NOT authenticated on '
1669 1669 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1670 1670 user, reason, loc, ip_access_valid, auth_token_access_valid)
1671 1671 # we preserve the get PARAM
1672 1672 came_from = get_came_from(request)
1673 1673
1674 1674 log.debug('redirecting to login page with %s', came_from)
1675 1675 raise HTTPFound(
1676 1676 h.route_path('login', _query={'came_from': came_from}))
1677 1677
1678 1678
1679 1679 class NotAnonymous(object):
1680 1680 """
1681 1681 Must be logged in to execute this function else
1682 1682 redirect to login page
1683 1683 """
1684 1684
1685 1685 def __call__(self, func):
1686 1686 return get_cython_compat_decorator(self.__wrapper, func)
1687 1687
1688 1688 def _get_request(self):
1689 1689 return get_request(self)
1690 1690
1691 1691 def __wrapper(self, func, *fargs, **fkwargs):
1692 1692 import rhodecode.lib.helpers as h
1693 1693 cls = fargs[0]
1694 1694 self.user = cls._rhodecode_user
1695 1695 request = self._get_request()
1696 1696 _ = request.translate
1697 1697 log.debug('Checking if user is not anonymous @%s', cls)
1698 1698
1699 1699 anonymous = self.user.username == User.DEFAULT_USER
1700 1700
1701 1701 if anonymous:
1702 1702 came_from = get_came_from(request)
1703 1703 h.flash(_('You need to be a registered user to '
1704 1704 'perform this action'),
1705 1705 category='warning')
1706 1706 raise HTTPFound(
1707 1707 h.route_path('login', _query={'came_from': came_from}))
1708 1708 else:
1709 1709 return func(*fargs, **fkwargs)
1710 1710
1711 1711
1712 1712 class PermsDecorator(object):
1713 1713 """
1714 1714 Base class for controller decorators, we extract the current user from
1715 1715 the class itself, which has it stored in base controllers
1716 1716 """
1717 1717
1718 1718 def __init__(self, *required_perms):
1719 1719 self.required_perms = set(required_perms)
1720 1720
1721 1721 def __call__(self, func):
1722 1722 return get_cython_compat_decorator(self.__wrapper, func)
1723 1723
1724 1724 def _get_request(self):
1725 1725 return get_request(self)
1726 1726
1727 1727 def __wrapper(self, func, *fargs, **fkwargs):
1728 1728 import rhodecode.lib.helpers as h
1729 1729 cls = fargs[0]
1730 1730 _user = cls._rhodecode_user
1731 1731 request = self._get_request()
1732 1732 _ = request.translate
1733 1733
1734 1734 log.debug('checking %s permissions %s for %s %s',
1735 1735 self.__class__.__name__, self.required_perms, cls, _user)
1736 1736
1737 1737 if self.check_permissions(_user):
1738 1738 log.debug('Permission granted for %s %s', cls, _user)
1739 1739 return func(*fargs, **fkwargs)
1740 1740
1741 1741 else:
1742 1742 log.debug('Permission denied for %s %s', cls, _user)
1743 1743 anonymous = _user.username == User.DEFAULT_USER
1744 1744
1745 1745 if anonymous:
1746 1746 came_from = get_came_from(self._get_request())
1747 1747 h.flash(_('You need to be signed in to view this page'),
1748 1748 category='warning')
1749 1749 raise HTTPFound(
1750 1750 h.route_path('login', _query={'came_from': came_from}))
1751 1751
1752 1752 else:
1753 1753 # redirect with 404 to prevent resource discovery
1754 1754 raise HTTPNotFound()
1755 1755
1756 1756 def check_permissions(self, user):
1757 1757 """Dummy function for overriding"""
1758 1758 raise NotImplementedError(
1759 1759 'You have to write this function in child class')
1760 1760
1761 1761
1762 1762 class HasPermissionAllDecorator(PermsDecorator):
1763 1763 """
1764 1764 Checks for access permission for all given predicates. All of them
1765 1765 have to be meet in order to fulfill the request
1766 1766 """
1767 1767
1768 1768 def check_permissions(self, user):
1769 1769 perms = user.permissions_with_scope({})
1770 1770 if self.required_perms.issubset(perms['global']):
1771 1771 return True
1772 1772 return False
1773 1773
1774 1774
1775 1775 class HasPermissionAnyDecorator(PermsDecorator):
1776 1776 """
1777 1777 Checks for access permission for any of given predicates. In order to
1778 1778 fulfill the request any of predicates must be meet
1779 1779 """
1780 1780
1781 1781 def check_permissions(self, user):
1782 1782 perms = user.permissions_with_scope({})
1783 1783 if self.required_perms.intersection(perms['global']):
1784 1784 return True
1785 1785 return False
1786 1786
1787 1787
1788 1788 class HasRepoPermissionAllDecorator(PermsDecorator):
1789 1789 """
1790 1790 Checks for access permission for all given predicates for specific
1791 1791 repository. All of them have to be meet in order to fulfill the request
1792 1792 """
1793 1793 def _get_repo_name(self):
1794 1794 _request = self._get_request()
1795 1795 return get_repo_slug(_request)
1796 1796
1797 1797 def check_permissions(self, user):
1798 1798 perms = user.permissions
1799 1799 repo_name = self._get_repo_name()
1800 1800
1801 1801 try:
1802 1802 user_perms = {perms['repositories'][repo_name]}
1803 1803 except KeyError:
1804 1804 log.debug('cannot locate repo with name: `%s` in permissions defs',
1805 1805 repo_name)
1806 1806 return False
1807 1807
1808 1808 log.debug('checking `%s` permissions for repo `%s`',
1809 1809 user_perms, repo_name)
1810 1810 if self.required_perms.issubset(user_perms):
1811 1811 return True
1812 1812 return False
1813 1813
1814 1814
1815 1815 class HasRepoPermissionAnyDecorator(PermsDecorator):
1816 1816 """
1817 1817 Checks for access permission for any of given predicates for specific
1818 1818 repository. In order to fulfill the request any of predicates must be meet
1819 1819 """
1820 1820 def _get_repo_name(self):
1821 1821 _request = self._get_request()
1822 1822 return get_repo_slug(_request)
1823 1823
1824 1824 def check_permissions(self, user):
1825 1825 perms = user.permissions
1826 1826 repo_name = self._get_repo_name()
1827 1827
1828 1828 try:
1829 1829 user_perms = {perms['repositories'][repo_name]}
1830 1830 except KeyError:
1831 1831 log.debug(
1832 1832 'cannot locate repo with name: `%s` in permissions defs',
1833 1833 repo_name)
1834 1834 return False
1835 1835
1836 1836 log.debug('checking `%s` permissions for repo `%s`',
1837 1837 user_perms, repo_name)
1838 1838 if self.required_perms.intersection(user_perms):
1839 1839 return True
1840 1840 return False
1841 1841
1842 1842
1843 1843 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1844 1844 """
1845 1845 Checks for access permission for all given predicates for specific
1846 1846 repository group. All of them have to be meet in order to
1847 1847 fulfill the request
1848 1848 """
1849 1849 def _get_repo_group_name(self):
1850 1850 _request = self._get_request()
1851 1851 return get_repo_group_slug(_request)
1852 1852
1853 1853 def check_permissions(self, user):
1854 1854 perms = user.permissions
1855 1855 group_name = self._get_repo_group_name()
1856 1856 try:
1857 1857 user_perms = {perms['repositories_groups'][group_name]}
1858 1858 except KeyError:
1859 1859 log.debug(
1860 1860 'cannot locate repo group with name: `%s` in permissions defs',
1861 1861 group_name)
1862 1862 return False
1863 1863
1864 1864 log.debug('checking `%s` permissions for repo group `%s`',
1865 1865 user_perms, group_name)
1866 1866 if self.required_perms.issubset(user_perms):
1867 1867 return True
1868 1868 return False
1869 1869
1870 1870
1871 1871 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1872 1872 """
1873 1873 Checks for access permission for any of given predicates for specific
1874 1874 repository group. In order to fulfill the request any
1875 1875 of predicates must be met
1876 1876 """
1877 1877 def _get_repo_group_name(self):
1878 1878 _request = self._get_request()
1879 1879 return get_repo_group_slug(_request)
1880 1880
1881 1881 def check_permissions(self, user):
1882 1882 perms = user.permissions
1883 1883 group_name = self._get_repo_group_name()
1884 1884
1885 1885 try:
1886 1886 user_perms = {perms['repositories_groups'][group_name]}
1887 1887 except KeyError:
1888 1888 log.debug(
1889 1889 'cannot locate repo group with name: `%s` in permissions defs',
1890 1890 group_name)
1891 1891 return False
1892 1892
1893 1893 log.debug('checking `%s` permissions for repo group `%s`',
1894 1894 user_perms, group_name)
1895 1895 if self.required_perms.intersection(user_perms):
1896 1896 return True
1897 1897 return False
1898 1898
1899 1899
1900 1900 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1901 1901 """
1902 1902 Checks for access permission for all given predicates for specific
1903 1903 user group. All of them have to be meet in order to fulfill the request
1904 1904 """
1905 1905 def _get_user_group_name(self):
1906 1906 _request = self._get_request()
1907 1907 return get_user_group_slug(_request)
1908 1908
1909 1909 def check_permissions(self, user):
1910 1910 perms = user.permissions
1911 1911 group_name = self._get_user_group_name()
1912 1912 try:
1913 1913 user_perms = {perms['user_groups'][group_name]}
1914 1914 except KeyError:
1915 1915 return False
1916 1916
1917 1917 if self.required_perms.issubset(user_perms):
1918 1918 return True
1919 1919 return False
1920 1920
1921 1921
1922 1922 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1923 1923 """
1924 1924 Checks for access permission for any of given predicates for specific
1925 1925 user group. In order to fulfill the request any of predicates must be meet
1926 1926 """
1927 1927 def _get_user_group_name(self):
1928 1928 _request = self._get_request()
1929 1929 return get_user_group_slug(_request)
1930 1930
1931 1931 def check_permissions(self, user):
1932 1932 perms = user.permissions
1933 1933 group_name = self._get_user_group_name()
1934 1934 try:
1935 1935 user_perms = {perms['user_groups'][group_name]}
1936 1936 except KeyError:
1937 1937 return False
1938 1938
1939 1939 if self.required_perms.intersection(user_perms):
1940 1940 return True
1941 1941 return False
1942 1942
1943 1943
1944 1944 # CHECK FUNCTIONS
1945 1945 class PermsFunction(object):
1946 1946 """Base function for other check functions"""
1947 1947
1948 1948 def __init__(self, *perms):
1949 1949 self.required_perms = set(perms)
1950 1950 self.repo_name = None
1951 1951 self.repo_group_name = None
1952 1952 self.user_group_name = None
1953 1953
1954 1954 def __bool__(self):
1955 1955 frame = inspect.currentframe()
1956 1956 stack_trace = traceback.format_stack(frame)
1957 1957 log.error('Checking bool value on a class instance of perm '
1958 1958 'function is not allowed: %s', ''.join(stack_trace))
1959 1959 # rather than throwing errors, here we always return False so if by
1960 1960 # accident someone checks truth for just an instance it will always end
1961 1961 # up in returning False
1962 1962 return False
1963 1963 __nonzero__ = __bool__
1964 1964
1965 1965 def __call__(self, check_location='', user=None):
1966 1966 if not user:
1967 1967 log.debug('Using user attribute from global request')
1968 1968 request = self._get_request()
1969 1969 user = request.user
1970 1970
1971 1971 # init auth user if not already given
1972 1972 if not isinstance(user, AuthUser):
1973 1973 log.debug('Wrapping user %s into AuthUser', user)
1974 1974 user = AuthUser(user.user_id)
1975 1975
1976 1976 cls_name = self.__class__.__name__
1977 1977 check_scope = self._get_check_scope(cls_name)
1978 1978 check_location = check_location or 'unspecified location'
1979 1979
1980 1980 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1981 1981 self.required_perms, user, check_scope, check_location)
1982 1982 if not user:
1983 1983 log.warning('Empty user given for permission check')
1984 1984 return False
1985 1985
1986 1986 if self.check_permissions(user):
1987 1987 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1988 1988 check_scope, user, check_location)
1989 1989 return True
1990 1990
1991 1991 else:
1992 1992 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1993 1993 check_scope, user, check_location)
1994 1994 return False
1995 1995
1996 1996 def _get_request(self):
1997 1997 return get_request(self)
1998 1998
1999 1999 def _get_check_scope(self, cls_name):
2000 2000 return {
2001 2001 'HasPermissionAll': 'GLOBAL',
2002 2002 'HasPermissionAny': 'GLOBAL',
2003 2003 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2004 2004 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2005 2005 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2006 2006 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2007 2007 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2008 2008 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2009 2009 }.get(cls_name, '?:%s' % cls_name)
2010 2010
2011 2011 def check_permissions(self, user):
2012 2012 """Dummy function for overriding"""
2013 2013 raise Exception('You have to write this function in child class')
2014 2014
2015 2015
2016 2016 class HasPermissionAll(PermsFunction):
2017 2017 def check_permissions(self, user):
2018 2018 perms = user.permissions_with_scope({})
2019 2019 if self.required_perms.issubset(perms.get('global')):
2020 2020 return True
2021 2021 return False
2022 2022
2023 2023
2024 2024 class HasPermissionAny(PermsFunction):
2025 2025 def check_permissions(self, user):
2026 2026 perms = user.permissions_with_scope({})
2027 2027 if self.required_perms.intersection(perms.get('global')):
2028 2028 return True
2029 2029 return False
2030 2030
2031 2031
2032 2032 class HasRepoPermissionAll(PermsFunction):
2033 2033 def __call__(self, repo_name=None, check_location='', user=None):
2034 2034 self.repo_name = repo_name
2035 2035 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2036 2036
2037 2037 def _get_repo_name(self):
2038 2038 if not self.repo_name:
2039 2039 _request = self._get_request()
2040 2040 self.repo_name = get_repo_slug(_request)
2041 2041 return self.repo_name
2042 2042
2043 2043 def check_permissions(self, user):
2044 2044 self.repo_name = self._get_repo_name()
2045 2045 perms = user.permissions
2046 2046 try:
2047 2047 user_perms = {perms['repositories'][self.repo_name]}
2048 2048 except KeyError:
2049 2049 return False
2050 2050 if self.required_perms.issubset(user_perms):
2051 2051 return True
2052 2052 return False
2053 2053
2054 2054
2055 2055 class HasRepoPermissionAny(PermsFunction):
2056 2056 def __call__(self, repo_name=None, check_location='', user=None):
2057 2057 self.repo_name = repo_name
2058 2058 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2059 2059
2060 2060 def _get_repo_name(self):
2061 2061 if not self.repo_name:
2062 2062 _request = self._get_request()
2063 2063 self.repo_name = get_repo_slug(_request)
2064 2064 return self.repo_name
2065 2065
2066 2066 def check_permissions(self, user):
2067 2067 self.repo_name = self._get_repo_name()
2068 2068 perms = user.permissions
2069 2069 try:
2070 2070 user_perms = {perms['repositories'][self.repo_name]}
2071 2071 except KeyError:
2072 2072 return False
2073 2073 if self.required_perms.intersection(user_perms):
2074 2074 return True
2075 2075 return False
2076 2076
2077 2077
2078 2078 class HasRepoGroupPermissionAny(PermsFunction):
2079 2079 def __call__(self, group_name=None, check_location='', user=None):
2080 2080 self.repo_group_name = group_name
2081 2081 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2082 2082
2083 2083 def check_permissions(self, user):
2084 2084 perms = user.permissions
2085 2085 try:
2086 2086 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2087 2087 except KeyError:
2088 2088 return False
2089 2089 if self.required_perms.intersection(user_perms):
2090 2090 return True
2091 2091 return False
2092 2092
2093 2093
2094 2094 class HasRepoGroupPermissionAll(PermsFunction):
2095 2095 def __call__(self, group_name=None, check_location='', user=None):
2096 2096 self.repo_group_name = group_name
2097 2097 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2098 2098
2099 2099 def check_permissions(self, user):
2100 2100 perms = user.permissions
2101 2101 try:
2102 2102 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2103 2103 except KeyError:
2104 2104 return False
2105 2105 if self.required_perms.issubset(user_perms):
2106 2106 return True
2107 2107 return False
2108 2108
2109 2109
2110 2110 class HasUserGroupPermissionAny(PermsFunction):
2111 2111 def __call__(self, user_group_name=None, check_location='', user=None):
2112 2112 self.user_group_name = user_group_name
2113 2113 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2114 2114
2115 2115 def check_permissions(self, user):
2116 2116 perms = user.permissions
2117 2117 try:
2118 2118 user_perms = {perms['user_groups'][self.user_group_name]}
2119 2119 except KeyError:
2120 2120 return False
2121 2121 if self.required_perms.intersection(user_perms):
2122 2122 return True
2123 2123 return False
2124 2124
2125 2125
2126 2126 class HasUserGroupPermissionAll(PermsFunction):
2127 2127 def __call__(self, user_group_name=None, check_location='', user=None):
2128 2128 self.user_group_name = user_group_name
2129 2129 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2130 2130
2131 2131 def check_permissions(self, user):
2132 2132 perms = user.permissions
2133 2133 try:
2134 2134 user_perms = {perms['user_groups'][self.user_group_name]}
2135 2135 except KeyError:
2136 2136 return False
2137 2137 if self.required_perms.issubset(user_perms):
2138 2138 return True
2139 2139 return False
2140 2140
2141 2141
2142 2142 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2143 2143 class HasPermissionAnyMiddleware(object):
2144 2144 def __init__(self, *perms):
2145 2145 self.required_perms = set(perms)
2146 2146
2147 2147 def __call__(self, auth_user, repo_name):
2148 2148 # repo_name MUST be unicode, since we handle keys in permission
2149 2149 # dict by unicode
2150 2150 repo_name = safe_unicode(repo_name)
2151 2151 log.debug(
2152 2152 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2153 2153 self.required_perms, auth_user, repo_name)
2154 2154
2155 2155 if self.check_permissions(auth_user, repo_name):
2156 2156 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2157 2157 repo_name, auth_user, 'PermissionMiddleware')
2158 2158 return True
2159 2159
2160 2160 else:
2161 2161 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2162 2162 repo_name, auth_user, 'PermissionMiddleware')
2163 2163 return False
2164 2164
2165 2165 def check_permissions(self, user, repo_name):
2166 2166 perms = user.permissions_with_scope({'repo_name': repo_name})
2167 2167
2168 2168 try:
2169 2169 user_perms = {perms['repositories'][repo_name]}
2170 2170 except Exception:
2171 2171 log.exception('Error while accessing user permissions')
2172 2172 return False
2173 2173
2174 2174 if self.required_perms.intersection(user_perms):
2175 2175 return True
2176 2176 return False
2177 2177
2178 2178
2179 2179 # SPECIAL VERSION TO HANDLE API AUTH
2180 2180 class _BaseApiPerm(object):
2181 2181 def __init__(self, *perms):
2182 2182 self.required_perms = set(perms)
2183 2183
2184 2184 def __call__(self, check_location=None, user=None, repo_name=None,
2185 2185 group_name=None, user_group_name=None):
2186 2186 cls_name = self.__class__.__name__
2187 2187 check_scope = 'global:%s' % (self.required_perms,)
2188 2188 if repo_name:
2189 2189 check_scope += ', repo_name:%s' % (repo_name,)
2190 2190
2191 2191 if group_name:
2192 2192 check_scope += ', repo_group_name:%s' % (group_name,)
2193 2193
2194 2194 if user_group_name:
2195 2195 check_scope += ', user_group_name:%s' % (user_group_name,)
2196 2196
2197 2197 log.debug('checking cls:%s %s %s @ %s',
2198 2198 cls_name, self.required_perms, check_scope, check_location)
2199 2199 if not user:
2200 2200 log.debug('Empty User passed into arguments')
2201 2201 return False
2202 2202
2203 2203 # process user
2204 2204 if not isinstance(user, AuthUser):
2205 2205 user = AuthUser(user.user_id)
2206 2206 if not check_location:
2207 2207 check_location = 'unspecified'
2208 2208 if self.check_permissions(user.permissions, repo_name, group_name,
2209 2209 user_group_name):
2210 2210 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2211 2211 check_scope, user, check_location)
2212 2212 return True
2213 2213
2214 2214 else:
2215 2215 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2216 2216 check_scope, user, check_location)
2217 2217 return False
2218 2218
2219 2219 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2220 2220 user_group_name=None):
2221 2221 """
2222 2222 implement in child class should return True if permissions are ok,
2223 2223 False otherwise
2224 2224
2225 2225 :param perm_defs: dict with permission definitions
2226 2226 :param repo_name: repo name
2227 2227 """
2228 2228 raise NotImplementedError()
2229 2229
2230 2230
2231 2231 class HasPermissionAllApi(_BaseApiPerm):
2232 2232 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2233 2233 user_group_name=None):
2234 2234 if self.required_perms.issubset(perm_defs.get('global')):
2235 2235 return True
2236 2236 return False
2237 2237
2238 2238
2239 2239 class HasPermissionAnyApi(_BaseApiPerm):
2240 2240 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2241 2241 user_group_name=None):
2242 2242 if self.required_perms.intersection(perm_defs.get('global')):
2243 2243 return True
2244 2244 return False
2245 2245
2246 2246
2247 2247 class HasRepoPermissionAllApi(_BaseApiPerm):
2248 2248 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2249 2249 user_group_name=None):
2250 2250 try:
2251 2251 _user_perms = {perm_defs['repositories'][repo_name]}
2252 2252 except KeyError:
2253 2253 log.warning(traceback.format_exc())
2254 2254 return False
2255 2255 if self.required_perms.issubset(_user_perms):
2256 2256 return True
2257 2257 return False
2258 2258
2259 2259
2260 2260 class HasRepoPermissionAnyApi(_BaseApiPerm):
2261 2261 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2262 2262 user_group_name=None):
2263 2263 try:
2264 2264 _user_perms = {perm_defs['repositories'][repo_name]}
2265 2265 except KeyError:
2266 2266 log.warning(traceback.format_exc())
2267 2267 return False
2268 2268 if self.required_perms.intersection(_user_perms):
2269 2269 return True
2270 2270 return False
2271 2271
2272 2272
2273 2273 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2274 2274 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2275 2275 user_group_name=None):
2276 2276 try:
2277 2277 _user_perms = {perm_defs['repositories_groups'][group_name]}
2278 2278 except KeyError:
2279 2279 log.warning(traceback.format_exc())
2280 2280 return False
2281 2281 if self.required_perms.intersection(_user_perms):
2282 2282 return True
2283 2283 return False
2284 2284
2285 2285
2286 2286 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2287 2287 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2288 2288 user_group_name=None):
2289 2289 try:
2290 2290 _user_perms = {perm_defs['repositories_groups'][group_name]}
2291 2291 except KeyError:
2292 2292 log.warning(traceback.format_exc())
2293 2293 return False
2294 2294 if self.required_perms.issubset(_user_perms):
2295 2295 return True
2296 2296 return False
2297 2297
2298 2298
2299 2299 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2300 2300 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2301 2301 user_group_name=None):
2302 2302 try:
2303 2303 _user_perms = {perm_defs['user_groups'][user_group_name]}
2304 2304 except KeyError:
2305 2305 log.warning(traceback.format_exc())
2306 2306 return False
2307 2307 if self.required_perms.intersection(_user_perms):
2308 2308 return True
2309 2309 return False
2310 2310
2311 2311
2312 2312 def check_ip_access(source_ip, allowed_ips=None):
2313 2313 """
2314 2314 Checks if source_ip is a subnet of any of allowed_ips.
2315 2315
2316 2316 :param source_ip:
2317 2317 :param allowed_ips: list of allowed ips together with mask
2318 2318 """
2319 2319 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2320 2320 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2321 2321 if isinstance(allowed_ips, (tuple, list, set)):
2322 2322 for ip in allowed_ips:
2323 2323 ip = safe_unicode(ip)
2324 2324 try:
2325 2325 network_address = ipaddress.ip_network(ip, strict=False)
2326 2326 if source_ip_address in network_address:
2327 2327 log.debug('IP %s is network %s', source_ip_address, network_address)
2328 2328 return True
2329 2329 # for any case we cannot determine the IP, don't crash just
2330 2330 # skip it and log as error, we want to say forbidden still when
2331 2331 # sending bad IP
2332 2332 except Exception:
2333 2333 log.error(traceback.format_exc())
2334 2334 continue
2335 2335 return False
2336 2336
2337 2337
2338 2338 def get_cython_compat_decorator(wrapper, func):
2339 2339 """
2340 2340 Creates a cython compatible decorator. The previously used
2341 2341 decorator.decorator() function seems to be incompatible with cython.
2342 2342
2343 2343 :param wrapper: __wrapper method of the decorator class
2344 2344 :param func: decorated function
2345 2345 """
2346 2346 @wraps(func)
2347 2347 def local_wrapper(*args, **kwds):
2348 2348 return wrapper(func, *args, **kwds)
2349 2349 local_wrapper.__wrapped__ = func
2350 2350 return local_wrapper
2351 2351
2352 2352
@@ -1,222 +1,222 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Access Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th class="td-action"></th>
17 17 <th class="td-action"></th>
18 18 </tr>
19 19 ## USERS
20 20 %for _user in c.rhodecode_db_repo.permissions():
21 21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 22 <tr class="perm_admin_row">
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 27 <td class="td-user">
28 28 ${base.gravatar(_user.email, 16)}
29 29 ${h.link_to_user(_user.username)}
30 30 %if getattr(_user, 'admin_row', None):
31 31 (${_('super admin')})
32 32 %endif
33 33 %if getattr(_user, 'owner_row', None):
34 34 (${_('owner')})
35 35 %endif
36 36 </td>
37 37 <td></td>
38 38 <td class="quick_repo_menu">
39 39 % if c.rhodecode_user.is_admin:
40 40 <i class="icon-more"></i>
41 41 <div class="menu_items_container" style="display: none;">
42 42 <ul class="menu_items">
43 43 <li>
44 44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
45 45 </li>
46 46 </ul>
47 47 </div>
48 48 % endif
49 49 </td>
50 50 </tr>
51 51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
52 52 <tr>
53 53 <td colspan="4">
54 54 <span class="private_repo_msg">
55 55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
56 56 </span>
57 57 </td>
58 58 <td class="private_repo_msg">
59 59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
60 60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
61 61 <td></td>
62 62 <td class="quick_repo_menu">
63 63 % if c.rhodecode_user.is_admin:
64 64 <i class="icon-more"></i>
65 65 <div class="menu_items_container" style="display: none;">
66 66 <ul class="menu_items">
67 67 <li>
68 68 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
69 69 </li>
70 70 </ul>
71 71 </div>
72 72 % endif
73 73 </td>
74 74 </tr>
75 75 %else:
76 76 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
77 77 <tr>
78 78 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
79 79 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
80 80 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
81 81 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
82 82 <td class="td-user">
83 83 ${base.gravatar(_user.email, 16)}
84 84 <span class="user">
85 85 % if _user.username == h.DEFAULT_USER:
86 86 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
87 87 % else:
88 88 ${h.link_to_user(_user.username)}
89 89 %if getattr(_user, 'duplicate_perm', None):
90 90 (${_('inactive duplicate')})
91 91 %endif
92 92 %if getattr(_user, 'branch_rules', None):
93 93 % if used_by_n_rules == 1:
94 94 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
95 95 % else:
96 96 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
97 97 % endif
98 98 %endif
99 99 % endif
100 100 </span>
101 101 </td>
102 102 <td class="td-action">
103 103 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
104 104 <span class="btn btn-link btn-danger revoke_perm"
105 105 member="${_user.user_id}" member_type="user">
106 106 ${_('Remove')}
107 107 </span>
108 108 %elif _user.username == h.DEFAULT_USER:
109 109 <span class="tooltip btn btn-link btn-default" onclick="enablePrivateRepo(); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators.')}">
110 110 ${_('set private mode')}
111 111 </span>
112 112 %endif
113 113 </td>
114 114 <td class="quick_repo_menu">
115 115 % if c.rhodecode_user.is_admin:
116 116 <i class="icon-more"></i>
117 117 <div class="menu_items_container" style="display: none;">
118 118 <ul class="menu_items">
119 119 <li>
120 120 % if _user.username == h.DEFAULT_USER:
121 121 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
122 122 % else:
123 123 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
124 124 % endif
125 125 </li>
126 126 </ul>
127 127 </div>
128 128 % endif
129 129 </td>
130 130 </tr>
131 131 %endif
132 132 %endfor
133 133
134 134 ## USER GROUPS
135 135 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
136 136 <tr>
137 137 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
138 138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
139 139 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
140 140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
141 141 <td class="td-componentname">
142 142 <i class="icon-user-group"></i>
143 143 %if c.is_super_admin:
144 144 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
145 145 ${_user_group.users_group_name}
146 146 </a>
147 147 %else:
148 148 ${h.link_to_group(_user_group.users_group_name)}
149 149 %endif
150 150 (${_('members')}: ${len(_user_group.members)})
151 151 </td>
152 152 <td class="td-action">
153 153 <span class="btn btn-link btn-danger revoke_perm"
154 154 member="${_user_group.users_group_id}" member_type="user_group">
155 155 ${_('Remove')}
156 156 </span>
157 157 </td>
158 158 <td class="quick_repo_menu">
159 159 % if c.rhodecode_user.is_admin:
160 160 <i class="icon-more"></i>
161 161 <div class="menu_items_container" style="display: none;">
162 162 <ul class="menu_items">
163 163 <li>
164 164 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
165 165 </li>
166 166 </ul>
167 167 </div>
168 168 % endif
169 169 </td>
170 170 </tr>
171 171 %endfor
172 172 <tr class="new_members" id="add_perm_input"></tr>
173 173
174 174 <tr>
175 175 <td></td>
176 176 <td></td>
177 177 <td></td>
178 178 <td></td>
179 179 <td></td>
180 180 <td>
181 181 <span id="add_perm" class="link">
182 182 ${_('Add user/user group')}
183 183 </span>
184 184 </td>
185 185 <td></td>
186 186 </tr>
187 187
188 188 </table>
189 189
190 190 <div class="buttons">
191 191 ${h.submit('save',_('Save'),class_="btn btn-primary")}
192 192 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
193 193 </div>
194 194 ${h.end_form()}
195 195 </div>
196 196 </div>
197 197
198 198 <script type="text/javascript">
199 199 $('#add_perm').on('click', function(e){
200 200 addNewPermInput($(this), 'repository');
201 201 });
202 202 $('.revoke_perm').on('click', function(e){
203 203 markRevokePermInput($(this), 'repository');
204 204 });
205 205 quick_repo_menu();
206 206
207 207 var enablePrivateRepo = function () {
208 208 var postData = {
209 209 'csrf_token': CSRF_TOKEN
210 210 };
211 211
212 212 var success = function(o) {
213 213 var defaultUrl = pyroutes.url('edit_repo_perms', {"repo_name": templateContext.repo_name});
214 214 window.location = o.redirect_url || defaultUrl;
215 215 };
216 216
217 217 ajaxPOST(
218 218 pyroutes.url('edit_repo_perms_set_private', {"repo_name": templateContext.repo_name}),
219 219 postData,
220 220 success);
221 221 }
222 222 </script>
@@ -1,1063 +1,1063 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <%include file="/ejs_templates/templates.html"/>
5 5
6 6 <div class="outerwrapper">
7 7 <!-- HEADER -->
8 8 <div class="header">
9 9 <div id="header-inner" class="wrapper">
10 10 <div id="logo">
11 11 <div class="logo-wrapper">
12 12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 13 </div>
14 14 % if c.rhodecode_name:
15 15 <div class="branding">
16 16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 17 </div>
18 18 % endif
19 19 </div>
20 20 <!-- MENU BAR NAV -->
21 21 ${self.menu_bar_nav()}
22 22 <!-- END MENU BAR NAV -->
23 23 </div>
24 24 </div>
25 25 ${self.menu_bar_subnav()}
26 26 <!-- END HEADER -->
27 27
28 28 <!-- CONTENT -->
29 29 <div id="content" class="wrapper">
30 30
31 31 <rhodecode-toast id="notifications"></rhodecode-toast>
32 32
33 33 <div class="main">
34 34 ${next.main()}
35 35 </div>
36 36 </div>
37 37 <!-- END CONTENT -->
38 38
39 39 </div>
40 40 <!-- FOOTER -->
41 41 <div id="footer">
42 42 <div id="footer-inner" class="title wrapper">
43 43 <div>
44 44 <p class="footer-link-right">
45 45 % if c.visual.show_version:
46 46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 47 % endif
48 48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 49 % if c.visual.rhodecode_support_url:
50 50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 51 % endif
52 52 </p>
53 53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 54 <p class="server-instance" style="display:${sid}">
55 55 ## display hidden instance ID if specially defined
56 56 % if c.rhodecode_instanceid:
57 57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 58 % endif
59 59 </p>
60 60 </div>
61 61 </div>
62 62 </div>
63 63
64 64 <!-- END FOOTER -->
65 65
66 66 ### MAKO DEFS ###
67 67
68 68 <%def name="menu_bar_subnav()">
69 69 </%def>
70 70
71 71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 72 <div class="${class_}">
73 73 ${self.breadcrumbs_links()}
74 74 </div>
75 75 </%def>
76 76
77 77 <%def name="admin_menu(active=None)">
78 78 <%
79 79 def is_active(selected):
80 80 if selected == active:
81 81 return "active"
82 82 %>
83 83
84 84 <div id="context-bar">
85 85 <div class="wrapper">
86 86 <div class="title">
87 87 <div class="title-content">
88 88 <div class="title-main">
89 89 % if c.is_super_admin:
90 90 ${_('Super Admin Panel')}
91 91 % else:
92 92 ${_('Delegated Admin Panel')}
93 93 % endif
94 94 </div>
95 95 </div>
96 96 </div>
97 97
98 98 <ul id="context-pages" class="navigation horizontal-list">
99 99
100 100 ## super admin case
101 101 % if c.is_super_admin:
102 102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
103 103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
104 104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
105 105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
106 106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
107 107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
108 108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
109 109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
110 110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
111 111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
112 112
113 113 ## delegated admin
114 114 % elif c.is_delegated_admin:
115 115 <%
116 116 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 119 %>
120 120
121 121 %if repositories:
122 122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 123 %endif
124 124 %if repository_groups:
125 125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 126 %endif
127 127 %if user_groups:
128 128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 129 %endif
130 130 % endif
131 131 </ul>
132 132
133 133 </div>
134 134 <div class="clear"></div>
135 135 </div>
136 136 </%def>
137 137
138 138 <%def name="dt_info_panel(elements)">
139 139 <dl class="dl-horizontal">
140 140 %for dt, dd, title, show_items in elements:
141 141 <dt>${dt}:</dt>
142 142 <dd title="${h.tooltip(title)}">
143 143 %if callable(dd):
144 144 ## allow lazy evaluation of elements
145 145 ${dd()}
146 146 %else:
147 147 ${dd}
148 148 %endif
149 149 %if show_items:
150 150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 151 %endif
152 152 </dd>
153 153
154 154 %if show_items:
155 155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 156 %for item in show_items:
157 157 <dt></dt>
158 158 <dd>${item}</dd>
159 159 %endfor
160 160 </div>
161 161 %endif
162 162
163 163 %endfor
164 164 </dl>
165 165 </%def>
166 166
167 167 <%def name="gravatar(email, size=16)">
168 168 <%
169 169 if (size > 16):
170 170 gravatar_class = 'gravatar gravatar-large'
171 171 else:
172 172 gravatar_class = 'gravatar'
173 173 %>
174 174 <%doc>
175 175 TODO: johbo: For now we serve double size images to make it smooth
176 176 for retina. This is how it worked until now. Should be replaced
177 177 with a better solution at some point.
178 178 </%doc>
179 179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 180 </%def>
181 181
182 182
183 183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 184 <% email = h.email_or_none(contact) %>
185 185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 186 ${self.gravatar(email, size)}
187 187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 188 </div>
189 189 </%def>
190 190
191 191
192 192 <%def name="repo_page_title(repo_instance)">
193 193 <div class="title-content repo-title">
194 194
195 195 <div class="title-main">
196 196 ## SVN/HG/GIT icons
197 197 %if h.is_hg(repo_instance):
198 198 <i class="icon-hg"></i>
199 199 %endif
200 200 %if h.is_git(repo_instance):
201 201 <i class="icon-git"></i>
202 202 %endif
203 203 %if h.is_svn(repo_instance):
204 204 <i class="icon-svn"></i>
205 205 %endif
206 206
207 207 ## public/private
208 208 %if repo_instance.private:
209 209 <i class="icon-repo-private"></i>
210 210 %else:
211 211 <i class="icon-repo-public"></i>
212 212 %endif
213 213
214 214 ## repo name with group name
215 215 ${h.breadcrumb_repo_link(repo_instance)}
216 216
217 217 ## Context Actions
218 218 <div class="pull-right">
219 219 %if c.rhodecode_user.username != h.DEFAULT_USER:
220 220 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
221 221
222 222 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
223 223 % if c.repository_is_user_following:
224 224 <i class="icon-eye-off"></i>${_('Unwatch')}
225 225 % else:
226 226 <i class="icon-eye"></i>${_('Watch')}
227 227 % endif
228 228
229 229 </a>
230 230 %else:
231 231 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
232 232 %endif
233 233 </div>
234 234
235 235 </div>
236 236
237 237 ## FORKED
238 238 %if repo_instance.fork:
239 239 <p class="discreet">
240 240 <i class="icon-code-fork"></i> ${_('Fork of')}
241 241 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
242 242 </p>
243 243 %endif
244 244
245 245 ## IMPORTED FROM REMOTE
246 246 %if repo_instance.clone_uri:
247 247 <p class="discreet">
248 248 <i class="icon-code-fork"></i> ${_('Clone from')}
249 249 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
250 250 </p>
251 251 %endif
252 252
253 253 ## LOCKING STATUS
254 254 %if repo_instance.locked[0]:
255 255 <p class="locking_locked discreet">
256 256 <i class="icon-repo-lock"></i>
257 257 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
258 258 </p>
259 259 %elif repo_instance.enable_locking:
260 260 <p class="locking_unlocked discreet">
261 261 <i class="icon-repo-unlock"></i>
262 262 ${_('Repository not locked. Pull repository to lock it.')}
263 263 </p>
264 264 %endif
265 265
266 266 </div>
267 267 </%def>
268 268
269 269 <%def name="repo_menu(active=None)">
270 270 <%
271 271 def is_active(selected):
272 272 if selected == active:
273 273 return "active"
274 274 ## determine if we have "any" option available
275 275 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
276 276 has_actions = can_lock
277 277
278 278 %>
279 279 % if c.rhodecode_db_repo.archived:
280 280 <div class="alert alert-warning text-center">
281 281 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
282 282 </div>
283 283 % endif
284 284
285 285 <!--- REPO CONTEXT BAR -->
286 286 <div id="context-bar">
287 287 <div class="wrapper">
288 288
289 289 <div class="title">
290 290 ${self.repo_page_title(c.rhodecode_db_repo)}
291 291 </div>
292 292
293 293 <ul id="context-pages" class="navigation horizontal-list">
294 294 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
295 295 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
296 296 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
297 297 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
298 298
299 299 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
300 300 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
301 301 <li class="${is_active('showpullrequest')}">
302 302 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
303 303 <div class="menulabel">
304 304 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
305 305 </div>
306 306 </a>
307 307 </li>
308 308 %endif
309 309
310 310 <li class="${is_active('artifacts')}">
311 311 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
312 312 <div class="menulabel">
313 313 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
314 314 </div>
315 315 </a>
316 316 </li>
317 317
318 318 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
319 319 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
320 320 %endif
321 321
322 322 <li class="${is_active('options')}">
323 323 % if has_actions:
324 324 <a class="menulink dropdown">
325 325 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
326 326 </a>
327 327 <ul class="submenu">
328 328 %if can_lock:
329 329 %if c.rhodecode_db_repo.locked[0]:
330 330 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
331 331 %else:
332 332 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
333 333 %endif
334 334 %endif
335 335 </ul>
336 336 % else:
337 337 <a class="menulink disabled">
338 338 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
339 339 </a>
340 340 % endif
341 341 </li>
342 342
343 343 </ul>
344 344 </div>
345 345 <div class="clear"></div>
346 346 </div>
347 347
348 348 <!--- REPO END CONTEXT BAR -->
349 349
350 350 </%def>
351 351
352 352 <%def name="repo_group_page_title(repo_group_instance)">
353 353 <div class="title-content">
354 354 <div class="title-main">
355 355 ## Repository Group icon
356 356 <i class="icon-repo-group"></i>
357 357
358 358 ## repo name with group name
359 359 ${h.breadcrumb_repo_group_link(repo_group_instance)}
360 360 </div>
361 361
362 362 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
363 363 <div class="repo-group-desc discreet">
364 364 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
365 365 </div>
366 366
367 367 </div>
368 368 </%def>
369 369
370 370
371 371 <%def name="repo_group_menu(active=None)">
372 372 <%
373 373 def is_active(selected):
374 374 if selected == active:
375 375 return "active"
376 376
377 377 gr_name = c.repo_group.group_name if c.repo_group else None
378 378 # create repositories with write permission on group is set to true
379 379 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
380 380
381 381 %>
382 382
383 383
384 384 <!--- REPO GROUP CONTEXT BAR -->
385 385 <div id="context-bar">
386 386 <div class="wrapper">
387 387 <div class="title">
388 388 ${self.repo_group_page_title(c.repo_group)}
389 389 </div>
390 390
391 391 <ul id="context-pages" class="navigation horizontal-list">
392 392 <li class="${is_active('home')}">
393 393 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
394 394 </li>
395 395 % if c.is_super_admin or group_admin:
396 396 <li class="${is_active('settings')}">
397 397 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
398 398 </li>
399 399 % endif
400 400
401 401 </ul>
402 402 </div>
403 403 <div class="clear"></div>
404 404 </div>
405 405
406 406 <!--- REPO GROUP CONTEXT BAR -->
407 407
408 408 </%def>
409 409
410 410
411 411 <%def name="usermenu(active=False)">
412 412 <%
413 413 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
414 414
415 415 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
416 416 # create repositories with write permission on group is set to true
417 417
418 418 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
419 419 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
420 420 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
421 421 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
422 422
423 423 can_create_repos = c.is_super_admin or c.can_create_repo
424 424 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
425 425
426 426 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
427 427 can_create_repo_groups_in_group = c.is_super_admin or group_admin
428 428 %>
429 429
430 430 % if not_anonymous:
431 431 <%
432 432 default_target_group = dict()
433 433 if c.rhodecode_user.personal_repo_group:
434 434 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
435 435 %>
436 436
437 437 ## create action
438 438 <li>
439 439 <a href="#create-actions" onclick="return false;" class="menulink childs">
440 440 <i class="icon-plus-circled"></i>
441 441 </a>
442 442
443 443 <div class="action-menu submenu">
444 444
445 445 <ol>
446 446 ## scope of within a repository
447 447 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
448 448 <li class="submenu-title">${_('This Repository')}</li>
449 449 <li>
450 450 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
451 451 </li>
452 452 % if can_fork:
453 453 <li>
454 454 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
455 455 </li>
456 456 % endif
457 457 % endif
458 458
459 459 ## scope of within repository groups
460 460 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
461 461 <li class="submenu-title">${_('This Repository Group')}</li>
462 462
463 463 % if can_create_repos_in_group:
464 464 <li>
465 465 <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a>
466 466 </li>
467 467 % endif
468 468
469 469 % if can_create_repo_groups_in_group:
470 470 <li>
471 471 <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a>
472 472 </li>
473 473 % endif
474 474 % endif
475 475
476 476 ## personal group
477 477 % if c.rhodecode_user.personal_repo_group:
478 478 <li class="submenu-title">Personal Group</li>
479 479
480 480 <li>
481 481 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
482 482 </li>
483 483
484 484 <li>
485 485 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
486 486 </li>
487 487 % endif
488 488
489 489 ## Global actions
490 490 <li class="submenu-title">RhodeCode</li>
491 491 % if can_create_repos:
492 492 <li>
493 493 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
494 494 </li>
495 495 % endif
496 496
497 497 % if can_create_repo_groups:
498 498 <li>
499 499 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
500 500 </li>
501 501 % endif
502 502
503 503 <li>
504 504 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
505 505 </li>
506 506
507 507 </ol>
508 508
509 509 </div>
510 510 </li>
511 511
512 512 ## notifications
513 513 <li>
514 514 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
515 515 ${c.unread_notifications}
516 516 </a>
517 517 </li>
518 518 % endif
519 519
520 520 ## USER MENU
521 521 <li id="quick_login_li" class="${'active' if active else ''}">
522 522 % if c.rhodecode_user.username == h.DEFAULT_USER:
523 523 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
524 524 ${gravatar(c.rhodecode_user.email, 20)}
525 525 <span class="user">
526 526 <span>${_('Sign in')}</span>
527 527 </span>
528 528 </a>
529 529 % else:
530 530 ## logged in user
531 531 <a id="quick_login_link" class="menulink childs">
532 532 ${gravatar(c.rhodecode_user.email, 20)}
533 533 <span class="user">
534 534 <span class="menu_link_user">${c.rhodecode_user.username}</span>
535 535 <div class="show_more"></div>
536 536 </span>
537 537 </a>
538 538 ## subnav with menu for logged in user
539 539 <div class="user-menu submenu">
540 540 <div id="quick_login">
541 541 %if c.rhodecode_user.username != h.DEFAULT_USER:
542 542 <div class="">
543 543 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
544 544 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
545 545 <div class="email">${c.rhodecode_user.email}</div>
546 546 </div>
547 547 <div class="">
548 548 <ol class="links">
549 549 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
550 550 % if c.rhodecode_user.personal_repo_group:
551 551 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
552 552 % endif
553 553 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
554 554
555 555 % if c.debug_style:
556 556 <li>
557 557 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
558 558 <div class="menulabel">${_('[Style]')}</div>
559 559 </a>
560 560 </li>
561 561 % endif
562 562
563 563 ## bookmark-items
564 564 <li class="bookmark-items">
565 565 ${_('Bookmarks')}
566 566 <div class="pull-right">
567 567 <a href="${h.route_path('my_account_bookmarks')}">
568 568
569 569 <i class="icon-cog"></i>
570 570 </a>
571 571 </div>
572 572 </li>
573 573 % if not c.bookmark_items:
574 574 <li>
575 575 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
576 576 </li>
577 577 % endif
578 578 % for item in c.bookmark_items:
579 579 <li>
580 580 % if item.repository:
581 581 <div>
582 582 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
583 583 <code>${item.position}</code>
584 584 % if item.repository.repo_type == 'hg':
585 585 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
586 586 % elif item.repository.repo_type == 'git':
587 587 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
588 588 % elif item.repository.repo_type == 'svn':
589 589 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
590 590 % endif
591 591 ${(item.title or h.shorter(item.repository.repo_name, 30))}
592 592 </a>
593 593 </div>
594 594 % elif item.repository_group:
595 595 <div>
596 596 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
597 597 <code>${item.position}</code>
598 598 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
599 599 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
600 600 </a>
601 601 </div>
602 602 % else:
603 603 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
604 604 <code>${item.position}</code>
605 605 ${item.title}
606 606 </a>
607 607 % endif
608 608 </li>
609 609 % endfor
610 610
611 611 <li class="logout">
612 612 ${h.secure_form(h.route_path('logout'), request=request)}
613 613 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
614 614 ${h.end_form()}
615 615 </li>
616 616 </ol>
617 617 </div>
618 618 %endif
619 619 </div>
620 620 </div>
621 621
622 622 % endif
623 623 </li>
624 624 </%def>
625 625
626 626 <%def name="menu_items(active=None)">
627 627 <%
628 628 def is_active(selected):
629 629 if selected == active:
630 630 return "active"
631 631 return ""
632 632 %>
633 633
634 634 <ul id="quick" class="main_nav navigation horizontal-list">
635 635 ## notice box for important system messages
636 636 <li style="display: none">
637 637 <a class="notice-box" href="#openNotice" onclick="return false">
638 638 <div class="menulabel-notice" >
639 639 0
640 640 </div>
641 641 </a>
642 642 </li>
643 643
644 644 ## Main filter
645 645 <li>
646 646 <div class="menulabel main_filter_box">
647 647 <div class="main_filter_input_box">
648 648 <ul class="searchItems">
649 649
650 650 % if c.template_context['search_context']['repo_id']:
651 651 <li class="searchTag searchTagFilter searchTagHidable" >
652 652 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
653 653 <span class="tag">
654 654 This repo
655 655 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
656 656 </span>
657 657 ##</a>
658 658 </li>
659 659 % elif c.template_context['search_context']['repo_group_id']:
660 660 <li class="searchTag searchTagFilter searchTagHidable">
661 661 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
662 662 <span class="tag">
663 663 This group
664 664 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
665 665 </span>
666 666 ##</a>
667 667 </li>
668 668 % endif
669 669
670 670 <li class="searchTagInput">
671 671 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
672 672 </li>
673 673 <li class="searchTag searchTagHelp">
674 674 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
675 675 </li>
676 676 </ul>
677 677 </div>
678 678 </div>
679 679
680 680 <div id="main_filter_help" style="display: none">
681 681 - Use '/' key to quickly access this field.
682 682
683 683 - Enter a name of repository, or repository group for quick search.
684 684
685 685 - Prefix query to allow special search:
686 686
687 687 user:admin, to search for usernames, always global
688 688
689 689 user_group:devops, to search for user groups, always global
690 690
691 691 commit:efced4, to search for commits, scoped to repositories or groups
692 692
693 693 file:models.py, to search for file paths, scoped to repositories or groups
694 694
695 695 % if c.template_context['search_context']['repo_id']:
696 696 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
697 697 % elif c.template_context['search_context']['repo_group_id']:
698 698 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
699 699 % else:
700 700 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
701 701 % endif
702 702 </div>
703 703 </li>
704 704
705 705 ## ROOT MENU
706 706 <li class="${is_active('home')}">
707 707 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
708 708 <div class="menulabel">${_('Home')}</div>
709 709 </a>
710 710 </li>
711 711
712 712 %if c.rhodecode_user.username != h.DEFAULT_USER:
713 713 <li class="${is_active('journal')}">
714 714 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
715 715 <div class="menulabel">${_('Journal')}</div>
716 716 </a>
717 717 </li>
718 718 %else:
719 719 <li class="${is_active('journal')}">
720 720 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
721 721 <div class="menulabel">${_('Public journal')}</div>
722 722 </a>
723 723 </li>
724 724 %endif
725 725
726 726 <li class="${is_active('gists')}">
727 727 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
728 728 <div class="menulabel">${_('Gists')}</div>
729 729 </a>
730 730 </li>
731 731
732 732 % if c.is_super_admin or c.is_delegated_admin:
733 733 <li class="${is_active('admin')}">
734 734 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
735 735 <div class="menulabel">${_('Admin')} </div>
736 736 </a>
737 737 </li>
738 738 % endif
739 739
740 740 ## render extra user menu
741 741 ${usermenu(active=(active=='my_account'))}
742 742
743 743 </ul>
744 744
745 745 <script type="text/javascript">
746 746 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
747 747
748 748 var formatRepoResult = function(result, container, query, escapeMarkup) {
749 749 return function(data, escapeMarkup) {
750 750 if (!data.repo_id){
751 751 return data.text; // optgroup text Repositories
752 752 }
753 753
754 754 var tmpl = '';
755 755 var repoType = data['repo_type'];
756 756 var repoName = data['text'];
757 757
758 758 if(data && data.type == 'repo'){
759 759 if(repoType === 'hg'){
760 760 tmpl += '<i class="icon-hg"></i> ';
761 761 }
762 762 else if(repoType === 'git'){
763 763 tmpl += '<i class="icon-git"></i> ';
764 764 }
765 765 else if(repoType === 'svn'){
766 766 tmpl += '<i class="icon-svn"></i> ';
767 767 }
768 768 if(data['private']){
769 769 tmpl += '<i class="icon-lock" ></i> ';
770 770 }
771 771 else if(visualShowPublicIcon){
772 772 tmpl += '<i class="icon-unlock-alt"></i> ';
773 773 }
774 774 }
775 775 tmpl += escapeMarkup(repoName);
776 776 return tmpl;
777 777
778 778 }(result, escapeMarkup);
779 779 };
780 780
781 781 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
782 782 return function(data, escapeMarkup) {
783 783 if (!data.repo_group_id){
784 784 return data.text; // optgroup text Repositories
785 785 }
786 786
787 787 var tmpl = '';
788 788 var repoGroupName = data['text'];
789 789
790 790 if(data){
791 791
792 792 tmpl += '<i class="icon-repo-group"></i> ';
793 793
794 794 }
795 795 tmpl += escapeMarkup(repoGroupName);
796 796 return tmpl;
797 797
798 798 }(result, escapeMarkup);
799 799 };
800 800
801 801 var escapeRegExChars = function (value) {
802 802 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
803 803 };
804 804
805 805 var getRepoIcon = function(repo_type) {
806 806 if (repo_type === 'hg') {
807 807 return '<i class="icon-hg"></i> ';
808 808 }
809 809 else if (repo_type === 'git') {
810 810 return '<i class="icon-git"></i> ';
811 811 }
812 812 else if (repo_type === 'svn') {
813 813 return '<i class="icon-svn"></i> ';
814 814 }
815 815 return ''
816 816 };
817 817
818 818 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
819 819
820 820 if (value.split(':').length === 2) {
821 821 value = value.split(':')[1]
822 822 }
823 823
824 824 var searchType = data['type'];
825 825 var searchSubType = data['subtype'];
826 826 var valueDisplay = data['value_display'];
827 827
828 828 var pattern = '(' + escapeRegExChars(value) + ')';
829 829
830 830 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
831 831
832 832 // highlight match
833 833 if (searchType != 'text') {
834 834 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
835 835 }
836 836
837 837 var icon = '';
838 838
839 839 if (searchType === 'hint') {
840 840 icon += '<i class="icon-repo-group"></i> ';
841 841 }
842 842 // full text search/hints
843 843 else if (searchType === 'search') {
844 844 icon += '<i class="icon-more"></i> ';
845 845 if (searchSubType !== undefined && searchSubType == 'repo') {
846 846 valueDisplay += '<div class="pull-right tag">repository</div>';
847 847 }
848 848 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
849 849 valueDisplay += '<div class="pull-right tag">repo group</div>';
850 850 }
851 851 }
852 852 // repository
853 853 else if (searchType === 'repo') {
854 854
855 855 var repoIcon = getRepoIcon(data['repo_type']);
856 856 icon += repoIcon;
857 857
858 858 if (data['private']) {
859 859 icon += '<i class="icon-lock" ></i> ';
860 860 }
861 861 else if (visualShowPublicIcon) {
862 862 icon += '<i class="icon-unlock-alt"></i> ';
863 863 }
864 864 }
865 865 // repository groups
866 866 else if (searchType === 'repo_group') {
867 867 icon += '<i class="icon-repo-group"></i> ';
868 868 }
869 869 // user group
870 870 else if (searchType === 'user_group') {
871 871 icon += '<i class="icon-group"></i> ';
872 872 }
873 873 // user
874 874 else if (searchType === 'user') {
875 875 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
876 876 }
877 877 // commit
878 878 else if (searchType === 'commit') {
879 879 var repo_data = data['repo_data'];
880 880 var repoIcon = getRepoIcon(repo_data['repository_type']);
881 881 if (repoIcon) {
882 882 icon += repoIcon;
883 883 } else {
884 884 icon += '<i class="icon-tag"></i>';
885 885 }
886 886 }
887 887 // file
888 888 else if (searchType === 'file') {
889 889 var repo_data = data['repo_data'];
890 890 var repoIcon = getRepoIcon(repo_data['repository_type']);
891 891 if (repoIcon) {
892 892 icon += repoIcon;
893 893 } else {
894 894 icon += '<i class="icon-tag"></i>';
895 895 }
896 896 }
897 897 // generic text
898 898 else if (searchType === 'text') {
899 899 icon = '';
900 900 }
901 901
902 902 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
903 903 return tmpl.format(icon, valueDisplay);
904 904 };
905 905
906 906 var handleSelect = function(element, suggestion) {
907 907 if (suggestion.type === "hint") {
908 908 // we skip action
909 909 $('#main_filter').focus();
910 910 }
911 911 else if (suggestion.type === "text") {
912 912 // we skip action
913 913 $('#main_filter').focus();
914 914
915 915 } else {
916 916 window.location = suggestion['url'];
917 917 }
918 918 };
919 919
920 920 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
921 921 if (queryLowerCase.split(':').length === 2) {
922 922 queryLowerCase = queryLowerCase.split(':')[1]
923 923 }
924 924 if (suggestion.type === "text") {
925 925 // special case we don't want to "skip" display for
926 926 return true
927 927 }
928 928 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
929 929 };
930 930
931 931 var cleanContext = {
932 932 repo_view_type: null,
933 933
934 934 repo_id: null,
935 935 repo_name: "",
936 936
937 937 repo_group_id: null,
938 938 repo_group_name: null
939 939 };
940 940 var removeGoToFilter = function () {
941 941 $('.searchTagHidable').hide();
942 942 $('#main_filter').autocomplete(
943 943 'setOptions', {params:{search_context: cleanContext}});
944 944 };
945 945
946 946 $('#main_filter').autocomplete({
947 947 serviceUrl: pyroutes.url('goto_switcher_data'),
948 948 params: {
949 949 "search_context": templateContext.search_context
950 950 },
951 951 minChars:2,
952 952 maxHeight:400,
953 953 deferRequestBy: 300, //miliseconds
954 954 tabDisabled: true,
955 955 autoSelectFirst: false,
956 956 containerClass: 'autocomplete-qfilter-suggestions',
957 957 formatResult: autocompleteMainFilterFormatResult,
958 958 lookupFilter: autocompleteMainFilterResult,
959 959 onSelect: function (element, suggestion) {
960 960 handleSelect(element, suggestion);
961 961 return false;
962 962 },
963 963 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
964 964 if (jqXHR !== 'abort') {
965 965 alert("Error during search.\nError code: {0}".format(textStatus));
966 966 window.location = '';
967 967 }
968 968 }
969 969 });
970 970
971 971 showMainFilterBox = function () {
972 972 $('#main_filter_help').toggle();
973 973 };
974 974
975 975 $('#main_filter').on('keydown.autocomplete', function (e) {
976 976
977 977 var BACKSPACE = 8;
978 978 var el = $(e.currentTarget);
979 979 if(e.which === BACKSPACE){
980 980 var inputVal = el.val();
981 981 if (inputVal === ""){
982 982 removeGoToFilter()
983 983 }
984 984 }
985 985 });
986 986
987 987 </script>
988 988 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
989 989 </%def>
990 990
991 991 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
992 992 <div class="modal-dialog">
993 993 <div class="modal-content">
994 994 <div class="modal-header">
995 995 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
996 996 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
997 997 </div>
998 998 <div class="modal-body">
999 999 <div class="block-left">
1000 1000 <table class="keyboard-mappings">
1001 1001 <tbody>
1002 1002 <tr>
1003 1003 <th></th>
1004 1004 <th>${_('Site-wide shortcuts')}</th>
1005 1005 </tr>
1006 1006 <%
1007 1007 elems = [
1008 1008 ('/', 'Use quick search box'),
1009 1009 ('g h', 'Goto home page'),
1010 1010 ('g g', 'Goto my private gists page'),
1011 1011 ('g G', 'Goto my public gists page'),
1012 1012 ('g 0-9', 'Goto bookmarked items from 0-9'),
1013 1013 ('n r', 'New repository page'),
1014 1014 ('n g', 'New gist page'),
1015 1015 ]
1016 1016 %>
1017 1017 %for key, desc in elems:
1018 1018 <tr>
1019 1019 <td class="keys">
1020 1020 <span class="key tag">${key}</span>
1021 1021 </td>
1022 1022 <td>${desc}</td>
1023 1023 </tr>
1024 1024 %endfor
1025 1025 </tbody>
1026 1026 </table>
1027 1027 </div>
1028 1028 <div class="block-left">
1029 1029 <table class="keyboard-mappings">
1030 1030 <tbody>
1031 1031 <tr>
1032 1032 <th></th>
1033 1033 <th>${_('Repositories')}</th>
1034 1034 </tr>
1035 1035 <%
1036 1036 elems = [
1037 1037 ('g s', 'Goto summary page'),
1038 1038 ('g c', 'Goto changelog page'),
1039 1039 ('g f', 'Goto files page'),
1040 1040 ('g F', 'Goto files page with file search activated'),
1041 1041 ('g p', 'Goto pull requests page'),
1042 1042 ('g o', 'Goto repository settings'),
1043 ('g O', 'Goto repository permissions settings'),
1043 ('g O', 'Goto repository access permissions settings'),
1044 1044 ]
1045 1045 %>
1046 1046 %for key, desc in elems:
1047 1047 <tr>
1048 1048 <td class="keys">
1049 1049 <span class="key tag">${key}</span>
1050 1050 </td>
1051 1051 <td>${desc}</td>
1052 1052 </tr>
1053 1053 %endfor
1054 1054 </tbody>
1055 1055 </table>
1056 1056 </div>
1057 1057 </div>
1058 1058 <div class="modal-footer">
1059 1059 </div>
1060 1060 </div><!-- /.modal-content -->
1061 1061 </div><!-- /.modal-dialog -->
1062 1062 </div><!-- /.modal -->
1063 1063
@@ -1,372 +1,372 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 7 <% section_to_label = {
8 8 'global': 'Global Permissions',
9 9 'repository_branches': 'Repository Branch Rules',
10 'repositories': 'Repository Permissions',
10 'repositories': 'Repository Access Permissions',
11 11 'user_groups': 'User Group Permissions',
12 12 'repositories_groups': 'Repository Group Permissions',
13 13 } %>
14 14
15 15 <div id="perms" class="table fields">
16 16 %for section in sorted(permissions.keys(), key=lambda item: {'global': 0, 'repository_branches': 1}.get(item, 1000)):
17 17 <% total_counter = 0 %>
18 18
19 19 <div class="panel panel-default">
20 20 <div class="panel-heading" id="${section.replace("_","-")}-permissions">
21 21 <h3 class="panel-title">${section_to_label.get(section, section)} - <span id="total_count_${section}"></span>
22 22 <a class="permalink" href="#${section.replace("_","-")}-permissions"> ΒΆ</a>
23 23 </h3>
24 24 % if side_link:
25 25 <div class="pull-right">
26 26 <a href="${side_link}">${_('in JSON format')}</a>
27 27 </div>
28 28 % endif
29 29 </div>
30 30 <div class="panel-body">
31 31 <div class="perms_section_head field">
32 32 <div class="radios">
33 33 % if section == 'repository_branches':
34 34 <span class="permissions_boxes">
35 35 <span class="desc">${_('show')}: </span>
36 36 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
37 37 ${h.checkbox('perms_filter_merge_%s' % section, 'merge', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='merge')} <label for="${'perms_filter_merge_{}'.format(section)}"><span class="perm_tag merge">${_('merge')}</span></label>
38 38 ${h.checkbox('perms_filter_push_%s' % section, 'push', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push')} <label for="${'perms_filter_push_{}'.format(section)}"> <span class="perm_tag push">${_('push')}</span></label>
39 39 ${h.checkbox('perms_filter_push_force_%s' % section, 'push_force', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push_force')} <label for="${'perms_filter_push_force_{}'.format(section)}"><span class="perm_tag push_force">${_('push force')}</span></label>
40 40 </span>
41 41 % elif section != 'global':
42 42 <span class="permissions_boxes">
43 43 <span class="desc">${_('show')}: </span>
44 44 ${h.checkbox('perms_filter_none_%s' % section, 'none', '', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
45 45 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_{}'.format(section)}"><span class="perm_tag read">${_('read')}</span></label>
46 46 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_{}'.format(section)}"> <span class="perm_tag write">${_('write')}</span></label>
47 47 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_{}'.format(section)}"><span class="perm_tag admin">${_('admin')}</span></label>
48 48 </span>
49 49 % endif
50 50
51 51 </div>
52 52 </div>
53 53 <div class="field">
54 54 %if not permissions[section]:
55 55 <p class="empty_data help-block">${_('No permissions defined')}</p>
56 56 %else:
57 57 <div id='tbl_list_wrap_${section}'>
58 58 <table id="tbl_list_${section}" class="rctable">
59 59 ## global permission box
60 60 %if section == 'global':
61 61 <thead>
62 62 <tr>
63 63 <th colspan="2" class="left">${_('Permission')}</th>
64 64 %if actions:
65 65 <th colspan="2">${_('Edit Permission')}</th>
66 66 %endif
67 67 </thead>
68 68 <tbody>
69 69
70 70 <%
71 71 def get_section_perms(prefix, opts):
72 72 _selected = []
73 73 for op in opts:
74 74 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
75 75 _selected.append(op)
76 76 admin = 'hg.admin' in opts
77 77 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
78 78 return admin, _selected_vals, _selected
79 79 %>
80 80
81 81 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
82 82 <tr>
83 83 <td class="td-tags">
84 84 ${lbl}
85 85 </td>
86 86 <td class="td-tags">
87 87 %if val[0]:
88 88 %if not val_lbl:
89 89 ## super admin case
90 90 True
91 91 %else:
92 92 <span class="perm_tag admin">${val_lbl}.admin</span>
93 93 %endif
94 94 %else:
95 95 %if not val_lbl:
96 96 ${{'false': False,
97 97 'true': True,
98 98 'none': False,
99 99 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')}
100 100 %else:
101 101 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
102 102 %endif
103 103 %endif
104 104 </td>
105 105 %if actions:
106 106
107 107 % if edit_url or edit_global_url:
108 108
109 109 <td class="td-action">
110 110 % if edit_url:
111 111 <a href="${edit_url}">${_('edit')}</a>
112 112 % else:
113 113 -
114 114 % endif
115 115 </td>
116 116
117 117 <td class="td-action">
118 118 % if edit_global_url:
119 119 <a href="${edit_global_url}">${_('edit global')}</a>
120 120 % else:
121 121 -
122 122 % endif
123 123 </td>
124 124
125 125 % else:
126 126 <td class="td-action"></td>
127 127 <td class="td-action">
128 128 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
129 129 <td class="td-action">
130 130 % endif
131 131
132 132 %endif
133 133 </tr>
134 134 </%def>
135 135
136 136 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
137 137 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
138 138
139 139 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
140 140 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
141 141
142 142 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
143 143 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
144 144
145 145 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
146 146 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
147 147
148 148 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
149 149 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
150 150
151 151 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
152 152 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
153 153
154 154 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
155 155 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
156 156
157 157 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
158 158 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
159 159
160 160 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
161 161 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
162 162
163 163 </tbody>
164 164 ## Branch perms
165 165 %elif section == 'repository_branches':
166 166 <thead>
167 167 <tr>
168 168 <th>${_('Name')}</th>
169 169 <th>${_('Pattern')}</th>
170 170 <th>${_('Permission')}</th>
171 171 %if actions:
172 172 <th>${_('Edit Branch Permission')}</th>
173 173 %endif
174 174 </thead>
175 175 <tbody class="section_${section}">
176 176 <%
177 177 def name_sorter(permissions):
178 178 def custom_sorter(item):
179 179 return item[0]
180 180 return sorted(permissions, key=custom_sorter)
181 181
182 182 def branch_sorter(permissions):
183 183 def custom_sorter(item):
184 184 ## none, merge, push, push_force
185 185 section = item[1].split('.')[-1]
186 186 section_importance = {'none': u'0',
187 187 'merge': u'1',
188 188 'push': u'2',
189 189 'push_force': u'3'}.get(section)
190 190 ## sort by importance + name
191 191 return section_importance + item[0]
192 192 return sorted(permissions, key=custom_sorter)
193 193 %>
194 194 %for k, section_perms in name_sorter(permissions[section].items()):
195 195 ## for display purposes, for non super-admins we need to check if shown
196 196 ## repository is actually accessible for user
197 197 <% repo_perm = permissions['repositories'][k] %>
198 198 % if repo_perm == 'repository.none' and not c.rhodecode_user.is_admin:
199 199 ## skip this entry
200 200 <% continue %>
201 201 % endif
202 202
203 203 <% total_counter +=1 %>
204 204 % for pattern, perm in branch_sorter(section_perms.items()):
205 205 <tr class="perm_row ${'{}_{}'.format(section, perm.split('.')[-1])}">
206 206 <td class="td-name">
207 207 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
208 208 </td>
209 209 <td>${pattern}</td>
210 210 <td class="td-tags">
211 211 ## TODO: calculate origin somehow
212 212 ## % for i, ((_pat, perm), origin) in enumerate((permissions[section].perm_origin_stack[k])):
213 213
214 214 <div>
215 215 <% i = 0 %>
216 216 <% origin = 'unknown' %>
217 217 <% _css_class = i > 0 and 'perm_overriden' or '' %>
218 218
219 219 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
220 220 ${perm}
221 221 ##(${origin})
222 222 </span>
223 223 </div>
224 224 ## % endfor
225 225 </td>
226 226 %if actions:
227 227 <td class="td-action">
228 228 <a href="${h.route_path('edit_repo_perms_branch',repo_name=k)}">${_('edit')}</a>
229 229 </td>
230 230 %endif
231 231 </tr>
232 232 % endfor
233 233 %endfor
234 234 </tbody>
235 235
236 236 ## Repos/Repo Groups/users groups perms
237 237 %else:
238 238
239 239 ## none/read/write/admin permissions on groups/repos etc
240 240 <thead>
241 241 <tr>
242 242 <th>${_('Name')}</th>
243 243 <th>${_('Permission')}</th>
244 244 %if actions:
245 245 <th>${_('Edit Permission')}</th>
246 246 %endif
247 247 </thead>
248 248 <tbody class="section_${section}">
249 249 <%
250 250 def sorter(permissions):
251 251 def custom_sorter(item):
252 252 ## read/write/admin
253 253 section = item[1].split('.')[-1]
254 254 section_importance = {'none': u'0',
255 255 'read': u'1',
256 256 'write':u'2',
257 257 'admin':u'3'}.get(section)
258 258 ## sort by group importance+name
259 259 return section_importance+item[0]
260 260 return sorted(permissions, key=custom_sorter)
261 261 %>
262 262 %for k, section_perm in sorter(permissions[section].items()):
263 263 <% perm_value = section_perm.split('.')[-1] %>
264 264 <% _css_class = 'display:none' if perm_value in ['none'] else '' %>
265 265
266 266 %if perm_value != 'none' or show_all:
267 267 <tr class="perm_row ${'{}_{}'.format(section, section_perm.split('.')[-1])}" style="${_css_class}">
268 268 <td class="td-name">
269 269 %if section == 'repositories':
270 270 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
271 271 %elif section == 'repositories_groups':
272 272 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
273 273 %elif section == 'user_groups':
274 274 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
275 275 ${k}
276 276 %endif
277 277 </td>
278 278 <td class="td-tags">
279 279 %if hasattr(permissions[section], 'perm_origin_stack'):
280 280 <div>
281 281 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
282 282 <% _css_class = i > 0 and 'perm_overriden' or '' %>
283 283 % if i > 0:
284 284 <div style="color: #979797">
285 285 <i class="icon-arrow_up"></i>
286 286 ${_('overridden by')}
287 287 <i class="icon-arrow_up"></i>
288 288 </div>
289 289 % endif
290 290
291 291 <div>
292 292 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
293 293 ${perm} (${origin})
294 294 </span>
295 295 </div>
296 296
297 297 %endfor
298 298 </div>
299 299 %else:
300 300 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
301 301 %endif
302 302 </td>
303 303 %if actions:
304 304 <td class="td-action">
305 305 %if section == 'repositories':
306 306 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
307 307 %elif section == 'repositories_groups':
308 308 <a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
309 309 %elif section == 'user_groups':
310 310 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
311 311 %endif
312 312 </td>
313 313 %endif
314 314 </tr>
315 315 <% total_counter +=1 %>
316 316 %endif
317 317
318 318 %endfor
319 319
320 320 <tr id="empty_${section}" class="noborder" style="display:none;">
321 321 <td colspan="6">${_('No matching permission defined')}</td>
322 322 </tr>
323 323
324 324 </tbody>
325 325 %endif
326 326 </table>
327 327 </div>
328 328 %endif
329 329 </div>
330 330 </div>
331 331 </div>
332 332
333 333 <script>
334 334 $('#total_count_${section}').html(${total_counter})
335 335 </script>
336 336
337 337 %endfor
338 338 </div>
339 339
340 340 <script>
341 341 $(document).ready(function(){
342 342 var showEmpty = function(section){
343 343 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
344 344 if(visible === 0){
345 345 $('#empty_{0}'.format(section)).show();
346 346 }
347 347 else{
348 348 $('#empty_{0}'.format(section)).hide();
349 349 }
350 350 };
351 351
352 352 $('.perm_filter').on('change', function(e){
353 353 var self = this;
354 354 var section = $(this).attr('section');
355 355
356 356 var opts = {};
357 357 var elems = $('.filter_' + section).each(function(el){
358 358 var perm_type = $(this).attr('perm_type');
359 359 var checked = this.checked;
360 360 opts[perm_type] = checked;
361 361 if(checked){
362 362 $('.'+section+'_'+perm_type).show();
363 363 }
364 364 else{
365 365 $('.'+section+'_'+perm_type).hide();
366 366 }
367 367 });
368 368 showEmpty(section);
369 369 })
370 370 })
371 371 </script>
372 372 </%def>
General Comments 0
You need to be logged in to leave comments. Login now