##// END OF EJS Templates
fix(permissions): fixed security problem with apply-to-children functionality breaking permissions for private repositories...
super-admin -
r5550:cb083474 default
parent child Browse files
Show More
@@ -1,886 +1,889 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2024 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 """
21 21 repo group model for RhodeCode
22 22 """
23 23
24 24 import os
25 25 import datetime
26 26 import itertools
27 27 import logging
28 28 import shutil
29 29 import time
30 30 import traceback
31 31 import string
32 32
33 from zope.cachedescriptors.property import Lazy as LazyProperty
34 33
35 34 from rhodecode import events
36 35 from rhodecode.model import BaseModel
37 36 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
38 37 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 38 UserGroup, Repository)
40 39 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
40 from rhodecode.model.settings import SettingsModel
42 41 from rhodecode.lib.caching_query import FromCache
43 42 from rhodecode.lib.utils2 import action_logger_generic
44 43
45 44 log = logging.getLogger(__name__)
46 45
47 46
48 47 class RepoGroupModel(BaseModel):
49 48
50 49 cls = RepoGroup
51 50 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 51 PERSONAL_GROUP_PATTERN = '${username}' # default
53 52
54 53 def _get_user_group(self, users_group):
55 54 return self._get_instance(UserGroup, users_group,
56 55 callback=UserGroup.get_by_group_name)
57 56
58 57 def _get_repo_group(self, repo_group):
59 58 return self._get_instance(RepoGroup, repo_group,
60 59 callback=RepoGroup.get_by_group_name)
61 60
62 61 def get_repo_group(self, repo_group):
63 62 return self._get_repo_group(repo_group)
64 63
65 64 def get_by_group_name(self, repo_group_name, cache=None):
66 65 repo = self.sa.query(RepoGroup) \
67 66 .filter(RepoGroup.group_name == repo_group_name)
68 67
69 68 if cache:
70 69 name_key = _hash_key(repo_group_name)
71 70 repo = repo.options(
72 71 FromCache("sql_cache_short", f"get_repo_group_{name_key}"))
73 72 return repo.scalar()
74 73
75 74 def get_default_create_personal_repo_group(self):
76 75 value = SettingsModel().get_setting_by_name(
77 76 'create_personal_repo_group')
78 77 return value.app_settings_value if value else None or False
79 78
80 79 def get_personal_group_name_pattern(self):
81 80 value = SettingsModel().get_setting_by_name(
82 81 'personal_repo_group_pattern')
83 82 val = value.app_settings_value if value else None
84 83 group_template = val or self.PERSONAL_GROUP_PATTERN
85 84
86 85 group_template = group_template.lstrip('/')
87 86 return group_template
88 87
89 88 def get_personal_group_name(self, user):
90 89 template = self.get_personal_group_name_pattern()
91 90 return string.Template(template).safe_substitute(
92 91 username=user.username,
93 92 user_id=user.user_id,
94 93 first_name=user.first_name,
95 94 last_name=user.last_name,
96 95 )
97 96
98 97 def create_personal_repo_group(self, user, commit_early=True):
99 98 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
100 99 personal_repo_group_name = self.get_personal_group_name(user)
101 100
102 101 # create a new one
103 102 RepoGroupModel().create(
104 103 group_name=personal_repo_group_name,
105 104 group_description=desc,
106 105 owner=user.username,
107 106 personal=True,
108 107 commit_early=commit_early)
109 108
110 109 def _create_default_perms(self, new_group):
111 110 # create default permission
112 111 default_perm = 'group.read'
113 112 def_user = User.get_default_user()
114 113 for p in def_user.user_perms:
115 114 if p.permission.permission_name.startswith('group.'):
116 115 default_perm = p.permission.permission_name
117 116 break
118 117
119 118 repo_group_to_perm = UserRepoGroupToPerm()
120 119 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
121 120
122 121 repo_group_to_perm.group = new_group
123 122 repo_group_to_perm.user = def_user
124 123 return repo_group_to_perm
125 124
126 125 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
127 126 get_object=False):
128 127 """
129 128 Get's the group name and a parent group name from given group name.
130 129 If repo_in_path is set to truth, we asume the full path also includes
131 130 repo name, in such case we clean the last element.
132 131
133 132 :param group_name_full:
134 133 """
135 134 split_paths = 1
136 135 if repo_in_path:
137 136 split_paths = 2
138 137 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
139 138
140 139 if repo_in_path and len(_parts) > 1:
141 140 # such case last element is the repo_name
142 141 _parts.pop(-1)
143 142 group_name_cleaned = _parts[-1] # just the group name
144 143 parent_repo_group_name = None
145 144
146 145 if len(_parts) > 1:
147 146 parent_repo_group_name = _parts[0]
148 147
149 148 parent_group = None
150 149 if parent_repo_group_name:
151 150 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
152 151
153 152 if get_object:
154 153 return group_name_cleaned, parent_repo_group_name, parent_group
155 154
156 155 return group_name_cleaned, parent_repo_group_name
157 156
158 157 def check_exist_filesystem(self, group_name, exc_on_failure=True):
159 158 create_path = os.path.join(self.repos_path, group_name)
160 159 log.debug('creating new group in %s', create_path)
161 160
162 161 if os.path.isdir(create_path):
163 162 if exc_on_failure:
164 163 abs_create_path = os.path.abspath(create_path)
165 164 raise Exception(f'Directory `{abs_create_path}` already exists !')
166 165 return False
167 166 return True
168 167
169 168 def _create_group(self, group_name):
170 169 """
171 170 makes repository group on filesystem
172 171
173 172 :param repo_name:
174 173 :param parent_id:
175 174 """
176 175
177 176 self.check_exist_filesystem(group_name)
178 177 create_path = os.path.join(self.repos_path, group_name)
179 178 log.debug('creating new group in %s', create_path)
180 179 os.makedirs(create_path, mode=0o755)
181 180 log.debug('created group in %s', create_path)
182 181
183 182 def _rename_group(self, old, new):
184 183 """
185 184 Renames a group on filesystem
186 185
187 186 :param group_name:
188 187 """
189 188
190 189 if old == new:
191 190 log.debug('skipping group rename')
192 191 return
193 192
194 193 log.debug('renaming repository group from %s to %s', old, new)
195 194
196 195 old_path = os.path.join(self.repos_path, old)
197 196 new_path = os.path.join(self.repos_path, new)
198 197
199 198 log.debug('renaming repos paths from %s to %s', old_path, new_path)
200 199
201 200 if os.path.isdir(new_path):
202 201 raise Exception('Was trying to rename to already '
203 202 'existing dir %s' % new_path)
204 203 shutil.move(old_path, new_path)
205 204
206 205 def _delete_filesystem_group(self, group, force_delete=False):
207 206 """
208 207 Deletes a group from a filesystem
209 208
210 209 :param group: instance of group from database
211 210 :param force_delete: use shutil rmtree to remove all objects
212 211 """
213 212 paths = group.full_path.split(RepoGroup.url_sep())
214 213 paths = os.sep.join(paths)
215 214
216 215 rm_path = os.path.join(self.repos_path, paths)
217 216 log.info("Removing group %s", rm_path)
218 217 # delete only if that path really exists
219 218 if os.path.isdir(rm_path):
220 219 if force_delete:
221 220 shutil.rmtree(rm_path)
222 221 else:
223 222 # archive that group`
224 223 _now = datetime.datetime.now()
225 224 _ms = str(_now.microsecond).rjust(6, '0')
226 225 _d = 'rm__{}_GROUP_{}'.format(
227 226 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
228 227 shutil.move(rm_path, os.path.join(self.repos_path, _d))
229 228
230 229 def create(self, group_name, group_description, owner, just_db=False,
231 230 copy_permissions=False, personal=None, commit_early=True):
232 231
233 232 (group_name_cleaned,
234 233 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
235 234
236 235 parent_group = None
237 236 if parent_group_name:
238 237 parent_group = self._get_repo_group(parent_group_name)
239 238 if not parent_group:
240 239 # we tried to create a nested group, but the parent is not
241 240 # existing
242 241 raise ValueError(
243 242 'Parent group `%s` given in `%s` group name '
244 243 'is not yet existing.' % (parent_group_name, group_name))
245 244
246 245 # because we are doing a cleanup, we need to check if such directory
247 246 # already exists. If we don't do that we can accidentally delete
248 247 # existing directory via cleanup that can cause data issues, since
249 248 # delete does a folder rename to special syntax later cleanup
250 249 # functions can delete this
251 250 cleanup_group = self.check_exist_filesystem(group_name,
252 251 exc_on_failure=False)
253 252 user = self._get_user(owner)
254 253 if not user:
255 254 raise ValueError('Owner %s not found as rhodecode user', owner)
256 255
257 256 try:
258 257 new_repo_group = RepoGroup()
259 258 new_repo_group.user = user
260 259 new_repo_group.group_description = group_description or group_name
261 260 new_repo_group.parent_group = parent_group
262 261 new_repo_group.group_name = group_name
263 262 new_repo_group.personal = personal
264 263
265 264 self.sa.add(new_repo_group)
266 265
267 266 # create an ADMIN permission for owner except if we're super admin,
268 267 # later owner should go into the owner field of groups
269 268 if not user.is_admin:
270 269 self.grant_user_permission(repo_group=new_repo_group,
271 270 user=owner, perm='group.admin')
272 271
273 272 if parent_group and copy_permissions:
274 273 # copy permissions from parent
275 274 user_perms = UserRepoGroupToPerm.query() \
276 275 .filter(UserRepoGroupToPerm.group == parent_group).all()
277 276
278 277 group_perms = UserGroupRepoGroupToPerm.query() \
279 278 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
280 279
281 280 for perm in user_perms:
282 281 # don't copy over the permission for user who is creating
283 282 # this group, if he is not super admin he get's admin
284 283 # permission set above
285 284 if perm.user != user or user.is_admin:
286 285 UserRepoGroupToPerm.create(
287 286 perm.user, new_repo_group, perm.permission)
288 287
289 288 for perm in group_perms:
290 289 UserGroupRepoGroupToPerm.create(
291 290 perm.users_group, new_repo_group, perm.permission)
292 291 else:
293 292 perm_obj = self._create_default_perms(new_repo_group)
294 293 self.sa.add(perm_obj)
295 294
296 295 # now commit the changes, earlier so we are sure everything is in
297 296 # the database.
298 297 if commit_early:
299 298 self.sa.commit()
300 299 if not just_db:
301 300 self._create_group(new_repo_group.group_name)
302 301
303 302 # trigger the post hook
304 303 from rhodecode.lib import hooks_base
305 304 repo_group = RepoGroup.get_by_group_name(group_name)
306 305
307 306 # update repo group commit caches initially
308 307 repo_group.update_commit_cache()
309 308
310 309 hooks_base.create_repository_group(
311 310 created_by=user.username, **repo_group.get_dict())
312 311
313 312 # Trigger create event.
314 313 events.trigger(events.RepoGroupCreateEvent(repo_group))
315 314
316 315 return new_repo_group
317 316 except Exception:
318 317 self.sa.rollback()
319 318 log.exception('Exception occurred when creating repository group, '
320 319 'doing cleanup...')
321 320 # rollback things manually !
322 321 repo_group = RepoGroup.get_by_group_name(group_name)
323 322 if repo_group:
324 323 RepoGroup.delete(repo_group.group_id)
325 324 self.sa.commit()
326 325 if cleanup_group:
327 326 RepoGroupModel()._delete_filesystem_group(repo_group)
328 327 raise
329 328
330 329 def update_permissions(
331 330 self, repo_group, perm_additions=None, perm_updates=None,
332 331 perm_deletions=None, recursive=None, check_perms=True,
333 332 cur_user=None):
334 333 from rhodecode.model.repo import RepoModel
335 334 from rhodecode.lib.auth import HasUserGroupPermissionAny
336 335
337 336 if not perm_additions:
338 337 perm_additions = []
339 338 if not perm_updates:
340 339 perm_updates = []
341 340 if not perm_deletions:
342 341 perm_deletions = []
343 342
344 343 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
345 344
346 345 changes = {
347 346 'added': [],
348 347 'updated': [],
349 348 'deleted': [],
350 349 'default_user_changed': None
351 350 }
352 351
353 def _set_perm_user(obj, user, perm):
354 if isinstance(obj, RepoGroup):
355 self.grant_user_permission(
356 repo_group=obj, user=user, perm=perm)
357 elif isinstance(obj, Repository):
352 def _set_perm_user(_obj: RepoGroup | Repository, _user_obj: User, _perm):
353
354 if isinstance(_obj, RepoGroup):
355 self.grant_user_permission(repo_group=_obj, user=_user_obj, perm=_perm)
356 elif isinstance(_obj, Repository):
358 357 # private repos will not allow to change the default
359 358 # permissions using recursive mode
360 if obj.private and user == User.DEFAULT_USER:
359 if _obj.private and _user_obj.username == User.DEFAULT_USER:
360 log.debug('Skipping private repo %s for user %s', _obj, _user_obj)
361 361 return
362 362
363 # we set group permission but we have to switch to repo
364 # permission
365 perm = perm.replace('group.', 'repository.')
366 RepoModel().grant_user_permission(
367 repo=obj, user=user, perm=perm)
363 # we set group permission, we have to switch to repo permission definition
364 new_perm = _perm.replace('group.', 'repository.')
365 RepoModel().grant_user_permission(repo=_obj, user=_user_obj, perm=new_perm)
366
367 def _set_perm_group(_obj: RepoGroup | Repository, users_group: UserGroup, _perm):
368 if isinstance(_obj, RepoGroup):
369 self.grant_user_group_permission(repo_group=_obj, group_name=users_group, perm=_perm)
370 elif isinstance(_obj, Repository):
371 # we set group permission, we have to switch to repo permission definition
372 new_perm = _perm.replace('group.', 'repository.')
373 RepoModel().grant_user_group_permission(repo=_obj, group_name=users_group, perm=new_perm)
368 374
369 def _set_perm_group(obj, users_group, perm):
370 if isinstance(obj, RepoGroup):
371 self.grant_user_group_permission(
372 repo_group=obj, group_name=users_group, perm=perm)
373 elif isinstance(obj, Repository):
374 # we set group permission but we have to switch to repo
375 # permission
376 perm = perm.replace('group.', 'repository.')
377 RepoModel().grant_user_group_permission(
378 repo=obj, group_name=users_group, perm=perm)
375 def _revoke_perm_user(_obj: RepoGroup | Repository, _user_obj: User):
376 if isinstance(_obj, RepoGroup):
377 self.revoke_user_permission(repo_group=_obj, user=_user_obj)
378 elif isinstance(_obj, Repository):
379 # private repos will not allow to change the default
380 # permissions using recursive mode, also there's no revocation fo default user, just update
381 if _user_obj.username == User.DEFAULT_USER:
382 log.debug('Skipping private repo %s for user %s', _obj, _user_obj)
383 return
384 RepoModel().revoke_user_permission(repo=_obj, user=_user_obj)
379 385
380 def _revoke_perm_user(obj, user):
381 if isinstance(obj, RepoGroup):
382 self.revoke_user_permission(repo_group=obj, user=user)
383 elif isinstance(obj, Repository):
384 RepoModel().revoke_user_permission(repo=obj, user=user)
385
386 def _revoke_perm_group(obj, user_group):
387 if isinstance(obj, RepoGroup):
388 self.revoke_user_group_permission(
389 repo_group=obj, group_name=user_group)
390 elif isinstance(obj, Repository):
391 RepoModel().revoke_user_group_permission(
392 repo=obj, group_name=user_group)
386 def _revoke_perm_group(_obj: RepoGroup | Repository, user_group: UserGroup):
387 if isinstance(_obj, RepoGroup):
388 self.revoke_user_group_permission(repo_group=_obj, group_name=user_group)
389 elif isinstance(_obj, Repository):
390 RepoModel().revoke_user_group_permission(repo=_obj, group_name=user_group)
393 391
394 392 # start updates
395 393 log.debug('Now updating permissions for %s in recursive mode:%s',
396 394 repo_group, recursive)
397 395
398 396 # initialize check function, we'll call that multiple times
399 397 has_group_perm = HasUserGroupPermissionAny(*req_perms)
400 398
401 399 for obj in repo_group.recursive_groups_and_repos():
402 400 # iterated obj is an instance of a repos group or repository in
403 401 # that group, recursive option can be: none, repos, groups, all
404 402 if recursive == 'all':
405 403 obj = obj
406 404 elif recursive == 'repos':
407 405 # skip groups, other than this one
408 406 if isinstance(obj, RepoGroup) and not obj == repo_group:
409 407 continue
410 408 elif recursive == 'groups':
411 409 # skip repos
412 410 if isinstance(obj, Repository):
413 411 continue
414 412 else: # recursive == 'none':
415 413 # DEFAULT option - don't apply to iterated objects
416 414 # also we do a break at the end of this loop. if we are not
417 415 # in recursive mode
418 416 obj = repo_group
419 417
420 418 change_obj = obj.get_api_data()
421 419
422 420 # update permissions
423 421 for member_id, perm, member_type in perm_updates:
424 422 member_id = int(member_id)
425 423 if member_type == 'user':
426 member_name = User.get(member_id).username
424 member_obj = User.get(member_id)
425 member_name = member_obj.username
427 426 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
428 427 # NOTE(dan): detect if we changed permissions for default user
429 428 perm_obj = self.sa.query(UserRepoGroupToPerm) \
430 429 .filter(UserRepoGroupToPerm.user_id == member_id) \
431 430 .filter(UserRepoGroupToPerm.group == repo_group) \
432 431 .scalar()
433 432 if perm_obj and perm_obj.permission.permission_name != perm:
434 433 changes['default_user_changed'] = True
435 434
436 435 # this updates also current one if found
437 _set_perm_user(obj, user=member_id, perm=perm)
436 _set_perm_user(obj, member_obj, perm)
438 437 elif member_type == 'user_group':
439 member_name = UserGroup.get(member_id).users_group_name
440 if not check_perms or has_group_perm(member_name,
441 user=cur_user):
442 _set_perm_group(obj, users_group=member_id, perm=perm)
438 member_obj = UserGroup.get(member_id)
439 member_name = member_obj.users_group_name
440 if not check_perms or has_group_perm(member_name, user=cur_user):
441 _set_perm_group(obj, member_obj, perm)
443 442 else:
444 raise ValueError("member_type must be 'user' or 'user_group' "
445 "got {} instead".format(member_type))
443 raise ValueError(
444 f"member_type must be 'user' or 'user_group' got {member_type} instead"
445 )
446 446
447 447 changes['updated'].append(
448 448 {'change_obj': change_obj, 'type': member_type,
449 449 'id': member_id, 'name': member_name, 'new_perm': perm})
450 450
451 451 # set new permissions
452 452 for member_id, perm, member_type in perm_additions:
453 453 member_id = int(member_id)
454 454 if member_type == 'user':
455 member_name = User.get(member_id).username
456 _set_perm_user(obj, user=member_id, perm=perm)
455 member_obj = User.get(member_id)
456 member_name = member_obj.username
457 _set_perm_user(obj, member_obj, perm)
457 458 elif member_type == 'user_group':
458 459 # check if we have permissions to alter this usergroup
459 member_name = UserGroup.get(member_id).users_group_name
460 if not check_perms or has_group_perm(member_name,
461 user=cur_user):
462 _set_perm_group(obj, users_group=member_id, perm=perm)
460 member_obj = UserGroup.get(member_id)
461 member_name = member_obj.users_group_name
462 if not check_perms or has_group_perm(member_name, user=cur_user):
463 _set_perm_group(obj, member_obj, perm)
463 464 else:
464 raise ValueError("member_type must be 'user' or 'user_group' "
465 "got {} instead".format(member_type))
465 raise ValueError(
466 f"member_type must be 'user' or 'user_group' got {member_type} instead"
467 )
466 468
467 469 changes['added'].append(
468 470 {'change_obj': change_obj, 'type': member_type,
469 471 'id': member_id, 'name': member_name, 'new_perm': perm})
470 472
471 473 # delete permissions
472 474 for member_id, perm, member_type in perm_deletions:
473 475 member_id = int(member_id)
474 476 if member_type == 'user':
475 member_name = User.get(member_id).username
476 _revoke_perm_user(obj, user=member_id)
477 member_obj = User.get(member_id)
478 member_name = member_obj.username
479 _revoke_perm_user(obj, member_obj)
477 480 elif member_type == 'user_group':
478 481 # check if we have permissions to alter this usergroup
479 member_name = UserGroup.get(member_id).users_group_name
480 if not check_perms or has_group_perm(member_name,
481 user=cur_user):
482 _revoke_perm_group(obj, user_group=member_id)
482 member_obj = UserGroup.get(member_id)
483 member_name = member_obj.users_group_name
484 if not check_perms or has_group_perm(member_name, user=cur_user):
485 _revoke_perm_group(obj, member_obj)
483 486 else:
484 raise ValueError("member_type must be 'user' or 'user_group' "
485 "got {} instead".format(member_type))
486
487 raise ValueError(
488 f"member_type must be 'user' or 'user_group' got {member_type} instead"
489 )
487 490 changes['deleted'].append(
488 491 {'change_obj': change_obj, 'type': member_type,
489 492 'id': member_id, 'name': member_name, 'new_perm': perm})
490 493
491 494 # if it's not recursive call for all,repos,groups
492 495 # break the loop and don't proceed with other changes
493 496 if recursive not in ['all', 'repos', 'groups']:
494 497 break
495 498
496 499 return changes
497 500
498 501 def update(self, repo_group, form_data):
499 502 try:
500 503 repo_group = self._get_repo_group(repo_group)
501 504 old_path = repo_group.full_path
502 505
503 506 # change properties
504 507 if 'group_description' in form_data:
505 508 repo_group.group_description = form_data['group_description']
506 509
507 510 if 'enable_locking' in form_data:
508 511 repo_group.enable_locking = form_data['enable_locking']
509 512
510 513 if 'group_parent_id' in form_data:
511 514 parent_group = (
512 515 self._get_repo_group(form_data['group_parent_id']))
513 516 repo_group.group_parent_id = (
514 517 parent_group.group_id if parent_group else None)
515 518 repo_group.parent_group = parent_group
516 519
517 520 # mikhail: to update the full_path, we have to explicitly
518 521 # update group_name
519 522 group_name = form_data.get('group_name', repo_group.name)
520 523 repo_group.group_name = repo_group.get_new_name(group_name)
521 524
522 525 new_path = repo_group.full_path
523 526
524 527 affected_user_ids = []
525 528 if 'user' in form_data:
526 529 old_owner_id = repo_group.user.user_id
527 530 new_owner = User.get_by_username(form_data['user'])
528 531 repo_group.user = new_owner
529 532
530 533 if old_owner_id != new_owner.user_id:
531 534 affected_user_ids = [new_owner.user_id, old_owner_id]
532 535
533 536 self.sa.add(repo_group)
534 537
535 538 # iterate over all members of this groups and do fixes
536 539 # set locking if given
537 540 # if obj is a repoGroup also fix the name of the group according
538 541 # to the parent
539 542 # if obj is a Repo fix it's name
540 543 # this can be potentially heavy operation
541 544 for obj in repo_group.recursive_groups_and_repos():
542 545 # set the value from it's parent
543 546 obj.enable_locking = repo_group.enable_locking
544 547 if isinstance(obj, RepoGroup):
545 548 new_name = obj.get_new_name(obj.name)
546 549 log.debug('Fixing group %s to new name %s',
547 550 obj.group_name, new_name)
548 551 obj.group_name = new_name
549 552
550 553 elif isinstance(obj, Repository):
551 554 # we need to get all repositories from this new group and
552 555 # rename them accordingly to new group path
553 556 new_name = obj.get_new_name(obj.just_name)
554 557 log.debug('Fixing repo %s to new name %s',
555 558 obj.repo_name, new_name)
556 559 obj.repo_name = new_name
557 560
558 561 self.sa.add(obj)
559 562
560 563 self._rename_group(old_path, new_path)
561 564
562 565 # Trigger update event.
563 566 events.trigger(events.RepoGroupUpdateEvent(repo_group))
564 567
565 568 if affected_user_ids:
566 569 PermissionModel().trigger_permission_flush(affected_user_ids)
567 570
568 571 return repo_group
569 572 except Exception:
570 573 log.error(traceback.format_exc())
571 574 raise
572 575
573 576 def delete(self, repo_group, force_delete=False, fs_remove=True):
574 577 repo_group = self._get_repo_group(repo_group)
575 578 if not repo_group:
576 579 return False
577 580 try:
578 581 self.sa.delete(repo_group)
579 582 if fs_remove:
580 583 self._delete_filesystem_group(repo_group, force_delete)
581 584 else:
582 585 log.debug('skipping removal from filesystem')
583 586
584 587 # Trigger delete event.
585 588 events.trigger(events.RepoGroupDeleteEvent(repo_group))
586 589 return True
587 590
588 591 except Exception:
589 592 log.error('Error removing repo_group %s', repo_group)
590 593 raise
591 594
592 595 def grant_user_permission(self, repo_group, user, perm):
593 596 """
594 597 Grant permission for user on given repository group, or update
595 598 existing one if found
596 599
597 600 :param repo_group: Instance of RepoGroup, repositories_group_id,
598 601 or repositories_group name
599 602 :param user: Instance of User, user_id or username
600 603 :param perm: Instance of Permission, or permission_name
601 604 """
602 605
603 606 repo_group = self._get_repo_group(repo_group)
604 607 user = self._get_user(user)
605 608 permission = self._get_perm(perm)
606 609
607 610 # check if we have that permission already
608 611 obj = self.sa.query(UserRepoGroupToPerm)\
609 612 .filter(UserRepoGroupToPerm.user == user)\
610 613 .filter(UserRepoGroupToPerm.group == repo_group)\
611 614 .scalar()
612 615 if obj is None:
613 616 # create new !
614 617 obj = UserRepoGroupToPerm()
615 618 obj.group = repo_group
616 619 obj.user = user
617 620 obj.permission = permission
618 621 self.sa.add(obj)
619 622 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
620 623 action_logger_generic(
621 624 'granted permission: {} to user: {} on repogroup: {}'.format(
622 625 perm, user, repo_group), namespace='security.repogroup')
623 626 return obj
624 627
625 628 def revoke_user_permission(self, repo_group, user):
626 629 """
627 630 Revoke permission for user on given repository group
628 631
629 632 :param repo_group: Instance of RepoGroup, repositories_group_id,
630 633 or repositories_group name
631 634 :param user: Instance of User, user_id or username
632 635 """
633 636
634 637 repo_group = self._get_repo_group(repo_group)
635 638 user = self._get_user(user)
636 639
637 640 obj = self.sa.query(UserRepoGroupToPerm)\
638 641 .filter(UserRepoGroupToPerm.user == user)\
639 642 .filter(UserRepoGroupToPerm.group == repo_group)\
640 643 .scalar()
641 644 if obj:
642 645 self.sa.delete(obj)
643 646 log.debug('Revoked perm on %s on %s', repo_group, user)
644 647 action_logger_generic(
645 648 'revoked permission from user: {} on repogroup: {}'.format(
646 649 user, repo_group), namespace='security.repogroup')
647 650
648 651 def grant_user_group_permission(self, repo_group, group_name, perm):
649 652 """
650 653 Grant permission for user group on given repository group, or update
651 654 existing one if found
652 655
653 656 :param repo_group: Instance of RepoGroup, repositories_group_id,
654 657 or repositories_group name
655 658 :param group_name: Instance of UserGroup, users_group_id,
656 659 or user group name
657 660 :param perm: Instance of Permission, or permission_name
658 661 """
659 662 repo_group = self._get_repo_group(repo_group)
660 663 group_name = self._get_user_group(group_name)
661 664 permission = self._get_perm(perm)
662 665
663 666 # check if we have that permission already
664 667 obj = self.sa.query(UserGroupRepoGroupToPerm)\
665 668 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
666 669 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
667 670 .scalar()
668 671
669 672 if obj is None:
670 673 # create new
671 674 obj = UserGroupRepoGroupToPerm()
672 675
673 676 obj.group = repo_group
674 677 obj.users_group = group_name
675 678 obj.permission = permission
676 679 self.sa.add(obj)
677 680 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
678 681 action_logger_generic(
679 682 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
680 683 perm, group_name, repo_group), namespace='security.repogroup')
681 684 return obj
682 685
683 686 def revoke_user_group_permission(self, repo_group, group_name):
684 687 """
685 688 Revoke permission for user group on given repository group
686 689
687 690 :param repo_group: Instance of RepoGroup, repositories_group_id,
688 691 or repositories_group name
689 692 :param group_name: Instance of UserGroup, users_group_id,
690 693 or user group name
691 694 """
692 695 repo_group = self._get_repo_group(repo_group)
693 696 group_name = self._get_user_group(group_name)
694 697
695 698 obj = self.sa.query(UserGroupRepoGroupToPerm)\
696 699 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
697 700 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
698 701 .scalar()
699 702 if obj:
700 703 self.sa.delete(obj)
701 704 log.debug('Revoked perm to %s on %s', repo_group, group_name)
702 705 action_logger_generic(
703 706 'revoked permission from usergroup: {} on repogroup: {}'.format(
704 707 group_name, repo_group), namespace='security.repogroup')
705 708
706 709 @classmethod
707 710 def update_commit_cache(cls, repo_groups=None):
708 711 if not repo_groups:
709 712 repo_groups = RepoGroup.getAll()
710 713 for repo_group in repo_groups:
711 714 repo_group.update_commit_cache()
712 715
713 716 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
714 717 super_user_actions=False):
715 718
716 719 from pyramid.threadlocal import get_current_request
717 720 _render = get_current_request().get_partial_renderer(
718 721 'rhodecode:templates/data_table/_dt_elements.mako')
719 722 c = _render.get_call_context()
720 723 h = _render.get_helpers()
721 724
722 725 def quick_menu(repo_group_name):
723 726 return _render('quick_repo_group_menu', repo_group_name)
724 727
725 728 def repo_group_lnk(repo_group_name):
726 729 return _render('repo_group_name', repo_group_name)
727 730
728 731 def last_change(last_change):
729 732 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
730 733 ts = time.time()
731 734 utc_offset = (datetime.datetime.fromtimestamp(ts)
732 735 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
733 736 last_change = last_change + datetime.timedelta(seconds=utc_offset)
734 737 return _render("last_change", last_change)
735 738
736 739 def desc(desc, personal):
737 740 return _render(
738 741 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
739 742
740 743 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
741 744 return _render(
742 745 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
743 746
744 747 def repo_group_name(repo_group_name, children_groups):
745 748 return _render("repo_group_name", repo_group_name, children_groups)
746 749
747 750 def user_profile(username):
748 751 return _render('user_profile', username)
749 752
750 753 repo_group_data = []
751 754 for group in repo_group_list:
752 755 # NOTE(marcink): because we use only raw column we need to load it like that
753 756 changeset_cache = RepoGroup._load_changeset_cache(
754 757 '', group._changeset_cache)
755 758 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
756 759 row = {
757 760 "menu": quick_menu(group.group_name),
758 761 "name": repo_group_lnk(group.group_name),
759 762 "name_raw": group.group_name,
760 763
761 764 "last_change": last_change(last_commit_change),
762 765
763 766 "last_changeset": "",
764 767 "last_changeset_raw": "",
765 768
766 769 "desc": desc(h.escape(group.group_description), group.personal),
767 770 "top_level_repos": 0,
768 771 "owner": user_profile(group.User.username)
769 772 }
770 773 if admin:
771 774 repo_count = group.repositories.count()
772 775 children_groups = list(map(
773 776 h.safe_str,
774 777 itertools.chain((g.name for g in group.parents),
775 778 (x.name for x in [group]))))
776 779 row.update({
777 780 "action": repo_group_actions(
778 781 group.group_id, group.group_name, repo_count),
779 782 "top_level_repos": repo_count,
780 783 "name": repo_group_name(group.group_name, children_groups),
781 784
782 785 })
783 786 repo_group_data.append(row)
784 787
785 788 return repo_group_data
786 789
787 790 def get_repo_groups_data_table(
788 791 self, draw, start, limit,
789 792 search_q, order_by, order_dir,
790 793 auth_user, repo_group_id):
791 794 from rhodecode.model.scm import RepoGroupList
792 795
793 796 _perms = ['group.read', 'group.write', 'group.admin']
794 797 repo_groups = RepoGroup.query() \
795 798 .filter(RepoGroup.group_parent_id == repo_group_id) \
796 799 .all()
797 800 auth_repo_group_list = RepoGroupList(
798 801 repo_groups, perm_set=_perms,
799 802 extra_kwargs=dict(user=auth_user))
800 803
801 804 allowed_ids = [-1]
802 805 for repo_group in auth_repo_group_list:
803 806 allowed_ids.append(repo_group.group_id)
804 807
805 808 repo_groups_data_total_count = RepoGroup.query() \
806 809 .filter(RepoGroup.group_parent_id == repo_group_id) \
807 810 .filter(or_(
808 811 # generate multiple IN to fix limitation problems
809 812 *in_filter_generator(RepoGroup.group_id, allowed_ids))
810 813 ) \
811 814 .count()
812 815
813 816 base_q = Session.query(
814 817 RepoGroup.group_name,
815 818 RepoGroup.group_name_hash,
816 819 RepoGroup.group_description,
817 820 RepoGroup.group_id,
818 821 RepoGroup.personal,
819 822 RepoGroup.updated_on,
820 823 RepoGroup._changeset_cache,
821 824 User,
822 825 ) \
823 826 .filter(RepoGroup.group_parent_id == repo_group_id) \
824 827 .filter(or_(
825 828 # generate multiple IN to fix limitation problems
826 829 *in_filter_generator(RepoGroup.group_id, allowed_ids))
827 830 ) \
828 831 .join(User, User.user_id == RepoGroup.user_id) \
829 832 .group_by(RepoGroup, User)
830 833
831 834 repo_groups_data_total_filtered_count = base_q.count()
832 835
833 836 sort_defined = False
834 837
835 838 if order_by == 'group_name':
836 839 sort_col = func.lower(RepoGroup.group_name)
837 840 sort_defined = True
838 841 elif order_by == 'user_username':
839 842 sort_col = User.username
840 843 else:
841 844 sort_col = getattr(RepoGroup, order_by, None)
842 845
843 846 if sort_defined or sort_col:
844 847 if order_dir == 'asc':
845 848 sort_col = sort_col.asc()
846 849 else:
847 850 sort_col = sort_col.desc()
848 851
849 852 base_q = base_q.order_by(sort_col)
850 853 base_q = base_q.offset(start).limit(limit)
851 854
852 855 repo_group_list = base_q.all()
853 856
854 857 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
855 858 repo_group_list=repo_group_list, admin=False)
856 859
857 860 data = ({
858 861 'draw': draw,
859 862 'data': repo_groups_data,
860 863 'recordsTotal': repo_groups_data_total_count,
861 864 'recordsFiltered': repo_groups_data_total_filtered_count,
862 865 })
863 866 return data
864 867
865 868 def _get_defaults(self, repo_group_name):
866 869 repo_group = RepoGroup.get_by_group_name(repo_group_name)
867 870
868 871 if repo_group is None:
869 872 return None
870 873
871 874 defaults = repo_group.get_dict()
872 875 defaults['repo_group_name'] = repo_group.name
873 876 defaults['repo_group_description'] = repo_group.group_description
874 877 defaults['repo_group_enable_locking'] = repo_group.enable_locking
875 878
876 879 # we use -1 as this is how in HTML, we mark an empty group
877 880 defaults['repo_group'] = defaults['group_parent_id'] or -1
878 881
879 882 # fill owner
880 883 if repo_group.user:
881 884 defaults.update({'user': repo_group.user.username})
882 885 else:
883 886 replacement_user = User.get_first_super_admin().username
884 887 defaults.update({'user': replacement_user})
885 888
886 889 return defaults
General Comments 0
You need to be logged in to leave comments. Login now