##// END OF EJS Templates
permissions: flush permissions on owner changes for repo and repo groups.
milka -
r4662:2b36afd5 default
parent child Browse files
Show More
@@ -1,1172 +1,1183 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 os
22 22 import re
23 23 import shutil
24 24 import time
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28
29 29 from pyramid.threadlocal import get_current_request
30 30 from zope.cachedescriptors.property import Lazy as LazyProperty
31 31
32 32 from rhodecode import events
33 33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 34 from rhodecode.lib.caching_query import FromCache
35 35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 36 from rhodecode.lib import hooks_base
37 37 from rhodecode.lib.user_log_filter import user_log_filter
38 38 from rhodecode.lib.utils import make_db_config
39 39 from rhodecode.lib.utils2 import (
40 40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 41 get_current_rhodecode_user, safe_int, action_logger_generic)
42 42 from rhodecode.lib.vcs.backends import get_backend
43 43 from rhodecode.model import BaseModel
44 44 from rhodecode.model.db import (
45 45 _hash_key, func, case, joinedload, or_, in_filter_generator,
46 46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 from rhodecode.model.permission import PermissionModel
49 50 from rhodecode.model.settings import VcsSettingsModel
50 51
51 52 log = logging.getLogger(__name__)
52 53
53 54
54 55 class RepoModel(BaseModel):
55 56
56 57 cls = Repository
57 58
58 59 def _get_user_group(self, users_group):
59 60 return self._get_instance(UserGroup, users_group,
60 61 callback=UserGroup.get_by_group_name)
61 62
62 63 def _get_repo_group(self, repo_group):
63 64 return self._get_instance(RepoGroup, repo_group,
64 65 callback=RepoGroup.get_by_group_name)
65 66
66 67 def _create_default_perms(self, repository, private):
67 68 # create default permission
68 69 default = 'repository.read'
69 70 def_user = User.get_default_user()
70 71 for p in def_user.user_perms:
71 72 if p.permission.permission_name.startswith('repository.'):
72 73 default = p.permission.permission_name
73 74 break
74 75
75 76 default_perm = 'repository.none' if private else default
76 77
77 78 repo_to_perm = UserRepoToPerm()
78 79 repo_to_perm.permission = Permission.get_by_key(default_perm)
79 80
80 81 repo_to_perm.repository = repository
81 82 repo_to_perm.user_id = def_user.user_id
82 83
83 84 return repo_to_perm
84 85
85 86 @LazyProperty
86 87 def repos_path(self):
87 88 """
88 89 Gets the repositories root path from database
89 90 """
90 91 settings_model = VcsSettingsModel(sa=self.sa)
91 92 return settings_model.get_repos_location()
92 93
93 94 def get(self, repo_id):
94 95 repo = self.sa.query(Repository) \
95 96 .filter(Repository.repo_id == repo_id)
96 97
97 98 return repo.scalar()
98 99
99 100 def get_repo(self, repository):
100 101 return self._get_repo(repository)
101 102
102 103 def get_by_repo_name(self, repo_name, cache=False):
103 104 repo = self.sa.query(Repository) \
104 105 .filter(Repository.repo_name == repo_name)
105 106
106 107 if cache:
107 108 name_key = _hash_key(repo_name)
108 109 repo = repo.options(
109 110 FromCache("sql_cache_short", "get_repo_%s" % name_key))
110 111 return repo.scalar()
111 112
112 113 def _extract_id_from_repo_name(self, repo_name):
113 114 if repo_name.startswith('/'):
114 115 repo_name = repo_name.lstrip('/')
115 116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
116 117 if by_id_match:
117 118 return by_id_match.groups()[0]
118 119
119 120 def get_repo_by_id(self, repo_name):
120 121 """
121 122 Extracts repo_name by id from special urls.
122 123 Example url is _11/repo_name
123 124
124 125 :param repo_name:
125 126 :return: repo object if matched else None
126 127 """
127 128
128 129 try:
129 130 _repo_id = self._extract_id_from_repo_name(repo_name)
130 131 if _repo_id:
131 132 return self.get(_repo_id)
132 133 except Exception:
133 134 log.exception('Failed to extract repo_name from URL')
134 135
135 136 return None
136 137
137 138 def get_repos_for_root(self, root, traverse=False):
138 139 if traverse:
139 140 like_expression = u'{}%'.format(safe_unicode(root))
140 141 repos = Repository.query().filter(
141 142 Repository.repo_name.like(like_expression)).all()
142 143 else:
143 144 if root and not isinstance(root, RepoGroup):
144 145 raise ValueError(
145 146 'Root must be an instance '
146 147 'of RepoGroup, got:{} instead'.format(type(root)))
147 148 repos = Repository.query().filter(Repository.group == root).all()
148 149 return repos
149 150
150 151 def get_url(self, repo, request=None, permalink=False):
151 152 if not request:
152 153 request = get_current_request()
153 154
154 155 if not request:
155 156 return
156 157
157 158 if permalink:
158 159 return request.route_url(
159 160 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
160 161 else:
161 162 return request.route_url(
162 163 'repo_summary', repo_name=safe_str(repo.repo_name))
163 164
164 165 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
165 166 if not request:
166 167 request = get_current_request()
167 168
168 169 if not request:
169 170 return
170 171
171 172 if permalink:
172 173 return request.route_url(
173 174 'repo_commit', repo_name=safe_str(repo.repo_id),
174 175 commit_id=commit_id)
175 176
176 177 else:
177 178 return request.route_url(
178 179 'repo_commit', repo_name=safe_str(repo.repo_name),
179 180 commit_id=commit_id)
180 181
181 182 def get_repo_log(self, repo, filter_term):
182 183 repo_log = UserLog.query()\
183 184 .filter(or_(UserLog.repository_id == repo.repo_id,
184 185 UserLog.repository_name == repo.repo_name))\
185 186 .options(joinedload(UserLog.user))\
186 187 .options(joinedload(UserLog.repository))\
187 188 .order_by(UserLog.action_date.desc())
188 189
189 190 repo_log = user_log_filter(repo_log, filter_term)
190 191 return repo_log
191 192
192 193 @classmethod
193 194 def update_commit_cache(cls, repositories=None):
194 195 if not repositories:
195 196 repositories = Repository.getAll()
196 197 for repo in repositories:
197 198 repo.update_commit_cache()
198 199
199 200 def get_repos_as_dict(self, repo_list=None, admin=False,
200 201 super_user_actions=False, short_name=None):
201 202
202 203 _render = get_current_request().get_partial_renderer(
203 204 'rhodecode:templates/data_table/_dt_elements.mako')
204 205 c = _render.get_call_context()
205 206 h = _render.get_helpers()
206 207
207 208 def quick_menu(repo_name):
208 209 return _render('quick_menu', repo_name)
209 210
210 211 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
211 212 if short_name is not None:
212 213 short_name_var = short_name
213 214 else:
214 215 short_name_var = not admin
215 216 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
216 217 short_name=short_name_var, admin=False)
217 218
218 219 def last_change(last_change):
219 220 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
220 221 ts = time.time()
221 222 utc_offset = (datetime.datetime.fromtimestamp(ts)
222 223 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
223 224 last_change = last_change + datetime.timedelta(seconds=utc_offset)
224 225
225 226 return _render("last_change", last_change)
226 227
227 228 def rss_lnk(repo_name):
228 229 return _render("rss", repo_name)
229 230
230 231 def atom_lnk(repo_name):
231 232 return _render("atom", repo_name)
232 233
233 234 def last_rev(repo_name, cs_cache):
234 235 return _render('revision', repo_name, cs_cache.get('revision'),
235 236 cs_cache.get('raw_id'), cs_cache.get('author'),
236 237 cs_cache.get('message'), cs_cache.get('date'))
237 238
238 239 def desc(desc):
239 240 return _render('repo_desc', desc, c.visual.stylify_metatags)
240 241
241 242 def state(repo_state):
242 243 return _render("repo_state", repo_state)
243 244
244 245 def repo_actions(repo_name):
245 246 return _render('repo_actions', repo_name, super_user_actions)
246 247
247 248 def user_profile(username):
248 249 return _render('user_profile', username)
249 250
250 251 repos_data = []
251 252 for repo in repo_list:
252 253 # NOTE(marcink): because we use only raw column we need to load it like that
253 254 changeset_cache = Repository._load_changeset_cache(
254 255 repo.repo_id, repo._changeset_cache)
255 256
256 257 row = {
257 258 "menu": quick_menu(repo.repo_name),
258 259
259 260 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
260 261 repo.private, repo.archived, repo.fork),
261 262
262 263 "desc": desc(h.escape(repo.description)),
263 264
264 265 "last_change": last_change(repo.updated_on),
265 266
266 267 "last_changeset": last_rev(repo.repo_name, changeset_cache),
267 268 "last_changeset_raw": changeset_cache.get('revision'),
268 269
269 270 "owner": user_profile(repo.User.username),
270 271
271 272 "state": state(repo.repo_state),
272 273 "rss": rss_lnk(repo.repo_name),
273 274 "atom": atom_lnk(repo.repo_name),
274 275 }
275 276 if admin:
276 277 row.update({
277 278 "action": repo_actions(repo.repo_name),
278 279 })
279 280 repos_data.append(row)
280 281
281 282 return repos_data
282 283
283 284 def get_repos_data_table(
284 285 self, draw, start, limit,
285 286 search_q, order_by, order_dir,
286 287 auth_user, repo_group_id):
287 288 from rhodecode.model.scm import RepoList
288 289
289 290 _perms = ['repository.read', 'repository.write', 'repository.admin']
290 291
291 292 repos = Repository.query() \
292 293 .filter(Repository.group_id == repo_group_id) \
293 294 .all()
294 295 auth_repo_list = RepoList(
295 296 repos, perm_set=_perms,
296 297 extra_kwargs=dict(user=auth_user))
297 298
298 299 allowed_ids = [-1]
299 300 for repo in auth_repo_list:
300 301 allowed_ids.append(repo.repo_id)
301 302
302 303 repos_data_total_count = Repository.query() \
303 304 .filter(Repository.group_id == repo_group_id) \
304 305 .filter(or_(
305 306 # generate multiple IN to fix limitation problems
306 307 *in_filter_generator(Repository.repo_id, allowed_ids))
307 308 ) \
308 309 .count()
309 310
310 311 base_q = Session.query(
311 312 Repository.repo_id,
312 313 Repository.repo_name,
313 314 Repository.description,
314 315 Repository.repo_type,
315 316 Repository.repo_state,
316 317 Repository.private,
317 318 Repository.archived,
318 319 Repository.fork,
319 320 Repository.updated_on,
320 321 Repository._changeset_cache,
321 322 User,
322 323 ) \
323 324 .filter(Repository.group_id == repo_group_id) \
324 325 .filter(or_(
325 326 # generate multiple IN to fix limitation problems
326 327 *in_filter_generator(Repository.repo_id, allowed_ids))
327 328 ) \
328 329 .join(User, User.user_id == Repository.user_id) \
329 330 .group_by(Repository, User)
330 331
331 332 repos_data_total_filtered_count = base_q.count()
332 333
333 334 sort_defined = False
334 335 if order_by == 'repo_name':
335 336 sort_col = func.lower(Repository.repo_name)
336 337 sort_defined = True
337 338 elif order_by == 'user_username':
338 339 sort_col = User.username
339 340 else:
340 341 sort_col = getattr(Repository, order_by, None)
341 342
342 343 if sort_defined or sort_col:
343 344 if order_dir == 'asc':
344 345 sort_col = sort_col.asc()
345 346 else:
346 347 sort_col = sort_col.desc()
347 348
348 349 base_q = base_q.order_by(sort_col)
349 350 base_q = base_q.offset(start).limit(limit)
350 351
351 352 repos_list = base_q.all()
352 353
353 354 repos_data = RepoModel().get_repos_as_dict(
354 355 repo_list=repos_list, admin=False)
355 356
356 357 data = ({
357 358 'draw': draw,
358 359 'data': repos_data,
359 360 'recordsTotal': repos_data_total_count,
360 361 'recordsFiltered': repos_data_total_filtered_count,
361 362 })
362 363 return data
363 364
364 365 def _get_defaults(self, repo_name):
365 366 """
366 367 Gets information about repository, and returns a dict for
367 368 usage in forms
368 369
369 370 :param repo_name:
370 371 """
371 372
372 373 repo_info = Repository.get_by_repo_name(repo_name)
373 374
374 375 if repo_info is None:
375 376 return None
376 377
377 378 defaults = repo_info.get_dict()
378 379 defaults['repo_name'] = repo_info.just_name
379 380
380 381 groups = repo_info.groups_with_parents
381 382 parent_group = groups[-1] if groups else None
382 383
383 384 # we use -1 as this is how in HTML, we mark an empty group
384 385 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
385 386
386 387 keys_to_process = (
387 388 {'k': 'repo_type', 'strip': False},
388 389 {'k': 'repo_enable_downloads', 'strip': True},
389 390 {'k': 'repo_description', 'strip': True},
390 391 {'k': 'repo_enable_locking', 'strip': True},
391 392 {'k': 'repo_landing_rev', 'strip': True},
392 393 {'k': 'clone_uri', 'strip': False},
393 394 {'k': 'push_uri', 'strip': False},
394 395 {'k': 'repo_private', 'strip': True},
395 396 {'k': 'repo_enable_statistics', 'strip': True}
396 397 )
397 398
398 399 for item in keys_to_process:
399 400 attr = item['k']
400 401 if item['strip']:
401 402 attr = remove_prefix(item['k'], 'repo_')
402 403
403 404 val = defaults[attr]
404 405 if item['k'] == 'repo_landing_rev':
405 406 val = ':'.join(defaults[attr])
406 407 defaults[item['k']] = val
407 408 if item['k'] == 'clone_uri':
408 409 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
409 410 if item['k'] == 'push_uri':
410 411 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
411 412
412 413 # fill owner
413 414 if repo_info.user:
414 415 defaults.update({'user': repo_info.user.username})
415 416 else:
416 417 replacement_user = User.get_first_super_admin().username
417 418 defaults.update({'user': replacement_user})
418 419
419 420 return defaults
420 421
421 422 def update(self, repo, **kwargs):
422 423 try:
423 424 cur_repo = self._get_repo(repo)
424 425 source_repo_name = cur_repo.repo_name
426
427 affected_user_ids = []
425 428 if 'user' in kwargs:
426 cur_repo.user = User.get_by_username(kwargs['user'])
429 old_owner_id = cur_repo.user.user_id
430 new_owner = User.get_by_username(kwargs['user'])
431 cur_repo.user = new_owner
432
433 if old_owner_id != new_owner.user_id:
434 affected_user_ids = [new_owner.user_id, old_owner_id]
427 435
428 436 if 'repo_group' in kwargs:
429 437 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
430 438 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
431 439
432 440 update_keys = [
433 441 (1, 'repo_description'),
434 442 (1, 'repo_landing_rev'),
435 443 (1, 'repo_private'),
436 444 (1, 'repo_enable_downloads'),
437 445 (1, 'repo_enable_locking'),
438 446 (1, 'repo_enable_statistics'),
439 447 (0, 'clone_uri'),
440 448 (0, 'push_uri'),
441 449 (0, 'fork_id')
442 450 ]
443 451 for strip, k in update_keys:
444 452 if k in kwargs:
445 453 val = kwargs[k]
446 454 if strip:
447 455 k = remove_prefix(k, 'repo_')
448 456
449 457 setattr(cur_repo, k, val)
450 458
451 459 new_name = cur_repo.get_new_name(kwargs['repo_name'])
452 460 cur_repo.repo_name = new_name
453 461
454 462 # if private flag is set, reset default permission to NONE
455 463 if kwargs.get('repo_private'):
456 464 EMPTY_PERM = 'repository.none'
457 465 RepoModel().grant_user_permission(
458 466 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
459 467 )
460 468
461 469 # handle extra fields
462 470 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
463 471 k = RepositoryField.un_prefix_key(field)
464 472 ex_field = RepositoryField.get_by_key_name(
465 473 key=k, repo=cur_repo)
466 474 if ex_field:
467 475 ex_field.field_value = kwargs[field]
468 476 self.sa.add(ex_field)
469 477
470 478 self.sa.add(cur_repo)
471 479
472 480 if source_repo_name != new_name:
473 481 # rename repository
474 482 self._rename_filesystem_repo(
475 483 old=source_repo_name, new=new_name)
476 484
485 if affected_user_ids:
486 PermissionModel().trigger_permission_flush(affected_user_ids)
487
477 488 return cur_repo
478 489 except Exception:
479 490 log.error(traceback.format_exc())
480 491 raise
481 492
482 493 def _create_repo(self, repo_name, repo_type, description, owner,
483 494 private=False, clone_uri=None, repo_group=None,
484 495 landing_rev='rev:tip', fork_of=None,
485 496 copy_fork_permissions=False, enable_statistics=False,
486 497 enable_locking=False, enable_downloads=False,
487 498 copy_group_permissions=False,
488 499 state=Repository.STATE_PENDING):
489 500 """
490 501 Create repository inside database with PENDING state, this should be
491 502 only executed by create() repo. With exception of importing existing
492 503 repos
493 504 """
494 505 from rhodecode.model.scm import ScmModel
495 506
496 507 owner = self._get_user(owner)
497 508 fork_of = self._get_repo(fork_of)
498 509 repo_group = self._get_repo_group(safe_int(repo_group))
499 510
500 511 try:
501 512 repo_name = safe_unicode(repo_name)
502 513 description = safe_unicode(description)
503 514 # repo name is just a name of repository
504 515 # while repo_name_full is a full qualified name that is combined
505 516 # with name and path of group
506 517 repo_name_full = repo_name
507 518 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
508 519
509 520 new_repo = Repository()
510 521 new_repo.repo_state = state
511 522 new_repo.enable_statistics = False
512 523 new_repo.repo_name = repo_name_full
513 524 new_repo.repo_type = repo_type
514 525 new_repo.user = owner
515 526 new_repo.group = repo_group
516 527 new_repo.description = description or repo_name
517 528 new_repo.private = private
518 529 new_repo.archived = False
519 530 new_repo.clone_uri = clone_uri
520 531 new_repo.landing_rev = landing_rev
521 532
522 533 new_repo.enable_statistics = enable_statistics
523 534 new_repo.enable_locking = enable_locking
524 535 new_repo.enable_downloads = enable_downloads
525 536
526 537 if repo_group:
527 538 new_repo.enable_locking = repo_group.enable_locking
528 539
529 540 if fork_of:
530 541 parent_repo = fork_of
531 542 new_repo.fork = parent_repo
532 543
533 544 events.trigger(events.RepoPreCreateEvent(new_repo))
534 545
535 546 self.sa.add(new_repo)
536 547
537 548 EMPTY_PERM = 'repository.none'
538 549 if fork_of and copy_fork_permissions:
539 550 repo = fork_of
540 551 user_perms = UserRepoToPerm.query() \
541 552 .filter(UserRepoToPerm.repository == repo).all()
542 553 group_perms = UserGroupRepoToPerm.query() \
543 554 .filter(UserGroupRepoToPerm.repository == repo).all()
544 555
545 556 for perm in user_perms:
546 557 UserRepoToPerm.create(
547 558 perm.user, new_repo, perm.permission)
548 559
549 560 for perm in group_perms:
550 561 UserGroupRepoToPerm.create(
551 562 perm.users_group, new_repo, perm.permission)
552 563 # in case we copy permissions and also set this repo to private
553 564 # override the default user permission to make it a private repo
554 565 if private:
555 566 RepoModel(self.sa).grant_user_permission(
556 567 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
557 568
558 569 elif repo_group and copy_group_permissions:
559 570 user_perms = UserRepoGroupToPerm.query() \
560 571 .filter(UserRepoGroupToPerm.group == repo_group).all()
561 572
562 573 group_perms = UserGroupRepoGroupToPerm.query() \
563 574 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
564 575
565 576 for perm in user_perms:
566 577 perm_name = perm.permission.permission_name.replace(
567 578 'group.', 'repository.')
568 579 perm_obj = Permission.get_by_key(perm_name)
569 580 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
570 581
571 582 for perm in group_perms:
572 583 perm_name = perm.permission.permission_name.replace(
573 584 'group.', 'repository.')
574 585 perm_obj = Permission.get_by_key(perm_name)
575 586 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
576 587
577 588 if private:
578 589 RepoModel(self.sa).grant_user_permission(
579 590 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
580 591
581 592 else:
582 593 perm_obj = self._create_default_perms(new_repo, private)
583 594 self.sa.add(perm_obj)
584 595
585 596 # now automatically start following this repository as owner
586 597 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
587 598
588 599 # we need to flush here, in order to check if database won't
589 600 # throw any exceptions, create filesystem dirs at the very end
590 601 self.sa.flush()
591 602 events.trigger(events.RepoCreateEvent(new_repo))
592 603 return new_repo
593 604
594 605 except Exception:
595 606 log.error(traceback.format_exc())
596 607 raise
597 608
598 609 def create(self, form_data, cur_user):
599 610 """
600 611 Create repository using celery tasks
601 612
602 613 :param form_data:
603 614 :param cur_user:
604 615 """
605 616 from rhodecode.lib.celerylib import tasks, run_task
606 617 return run_task(tasks.create_repo, form_data, cur_user)
607 618
608 619 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
609 620 perm_deletions=None, check_perms=True,
610 621 cur_user=None):
611 622 if not perm_additions:
612 623 perm_additions = []
613 624 if not perm_updates:
614 625 perm_updates = []
615 626 if not perm_deletions:
616 627 perm_deletions = []
617 628
618 629 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
619 630
620 631 changes = {
621 632 'added': [],
622 633 'updated': [],
623 634 'deleted': [],
624 635 'default_user_changed': None
625 636 }
626 637
627 638 repo = self._get_repo(repo)
628 639
629 640 # update permissions
630 641 for member_id, perm, member_type in perm_updates:
631 642 member_id = int(member_id)
632 643 if member_type == 'user':
633 644 member_name = User.get(member_id).username
634 645 if member_name == User.DEFAULT_USER:
635 646 # NOTE(dan): detect if we changed permissions for default user
636 647 perm_obj = self.sa.query(UserRepoToPerm) \
637 648 .filter(UserRepoToPerm.user_id == member_id) \
638 649 .filter(UserRepoToPerm.repository == repo) \
639 650 .scalar()
640 651 if perm_obj and perm_obj.permission.permission_name != perm:
641 652 changes['default_user_changed'] = True
642 653
643 654 # this updates also current one if found
644 655 self.grant_user_permission(
645 656 repo=repo, user=member_id, perm=perm)
646 657 elif member_type == 'user_group':
647 658 # check if we have permissions to alter this usergroup
648 659 member_name = UserGroup.get(member_id).users_group_name
649 660 if not check_perms or HasUserGroupPermissionAny(
650 661 *req_perms)(member_name, user=cur_user):
651 662 self.grant_user_group_permission(
652 663 repo=repo, group_name=member_id, perm=perm)
653 664 else:
654 665 raise ValueError("member_type must be 'user' or 'user_group' "
655 666 "got {} instead".format(member_type))
656 667 changes['updated'].append({'type': member_type, 'id': member_id,
657 668 'name': member_name, 'new_perm': perm})
658 669
659 670 # set new permissions
660 671 for member_id, perm, member_type in perm_additions:
661 672 member_id = int(member_id)
662 673 if member_type == 'user':
663 674 member_name = User.get(member_id).username
664 675 self.grant_user_permission(
665 676 repo=repo, user=member_id, perm=perm)
666 677 elif member_type == 'user_group':
667 678 # check if we have permissions to alter this usergroup
668 679 member_name = UserGroup.get(member_id).users_group_name
669 680 if not check_perms or HasUserGroupPermissionAny(
670 681 *req_perms)(member_name, user=cur_user):
671 682 self.grant_user_group_permission(
672 683 repo=repo, group_name=member_id, perm=perm)
673 684 else:
674 685 raise ValueError("member_type must be 'user' or 'user_group' "
675 686 "got {} instead".format(member_type))
676 687
677 688 changes['added'].append({'type': member_type, 'id': member_id,
678 689 'name': member_name, 'new_perm': perm})
679 690 # delete permissions
680 691 for member_id, perm, member_type in perm_deletions:
681 692 member_id = int(member_id)
682 693 if member_type == 'user':
683 694 member_name = User.get(member_id).username
684 695 self.revoke_user_permission(repo=repo, user=member_id)
685 696 elif member_type == 'user_group':
686 697 # check if we have permissions to alter this usergroup
687 698 member_name = UserGroup.get(member_id).users_group_name
688 699 if not check_perms or HasUserGroupPermissionAny(
689 700 *req_perms)(member_name, user=cur_user):
690 701 self.revoke_user_group_permission(
691 702 repo=repo, group_name=member_id)
692 703 else:
693 704 raise ValueError("member_type must be 'user' or 'user_group' "
694 705 "got {} instead".format(member_type))
695 706
696 707 changes['deleted'].append({'type': member_type, 'id': member_id,
697 708 'name': member_name, 'new_perm': perm})
698 709 return changes
699 710
700 711 def create_fork(self, form_data, cur_user):
701 712 """
702 713 Simple wrapper into executing celery task for fork creation
703 714
704 715 :param form_data:
705 716 :param cur_user:
706 717 """
707 718 from rhodecode.lib.celerylib import tasks, run_task
708 719 return run_task(tasks.create_repo_fork, form_data, cur_user)
709 720
710 721 def archive(self, repo):
711 722 """
712 723 Archive given repository. Set archive flag.
713 724
714 725 :param repo:
715 726 """
716 727 repo = self._get_repo(repo)
717 728 if repo:
718 729
719 730 try:
720 731 repo.archived = True
721 732 self.sa.add(repo)
722 733 self.sa.commit()
723 734 except Exception:
724 735 log.error(traceback.format_exc())
725 736 raise
726 737
727 738 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
728 739 """
729 740 Delete given repository, forks parameter defines what do do with
730 741 attached forks. Throws AttachedForksError if deleted repo has attached
731 742 forks
732 743
733 744 :param repo:
734 745 :param forks: str 'delete' or 'detach'
735 746 :param pull_requests: str 'delete' or None
736 747 :param fs_remove: remove(archive) repo from filesystem
737 748 """
738 749 if not cur_user:
739 750 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
740 751 repo = self._get_repo(repo)
741 752 if repo:
742 753 if forks == 'detach':
743 754 for r in repo.forks:
744 755 r.fork = None
745 756 self.sa.add(r)
746 757 elif forks == 'delete':
747 758 for r in repo.forks:
748 759 self.delete(r, forks='delete')
749 760 elif [f for f in repo.forks]:
750 761 raise AttachedForksError()
751 762
752 763 # check for pull requests
753 764 pr_sources = repo.pull_requests_source
754 765 pr_targets = repo.pull_requests_target
755 766 if pull_requests != 'delete' and (pr_sources or pr_targets):
756 767 raise AttachedPullRequestsError()
757 768
758 769 old_repo_dict = repo.get_dict()
759 770 events.trigger(events.RepoPreDeleteEvent(repo))
760 771 try:
761 772 self.sa.delete(repo)
762 773 if fs_remove:
763 774 self._delete_filesystem_repo(repo)
764 775 else:
765 776 log.debug('skipping removal from filesystem')
766 777 old_repo_dict.update({
767 778 'deleted_by': cur_user,
768 779 'deleted_on': time.time(),
769 780 })
770 781 hooks_base.delete_repository(**old_repo_dict)
771 782 events.trigger(events.RepoDeleteEvent(repo))
772 783 except Exception:
773 784 log.error(traceback.format_exc())
774 785 raise
775 786
776 787 def grant_user_permission(self, repo, user, perm):
777 788 """
778 789 Grant permission for user on given repository, or update existing one
779 790 if found
780 791
781 792 :param repo: Instance of Repository, repository_id, or repository name
782 793 :param user: Instance of User, user_id or username
783 794 :param perm: Instance of Permission, or permission_name
784 795 """
785 796 user = self._get_user(user)
786 797 repo = self._get_repo(repo)
787 798 permission = self._get_perm(perm)
788 799
789 800 # check if we have that permission already
790 801 obj = self.sa.query(UserRepoToPerm) \
791 802 .filter(UserRepoToPerm.user == user) \
792 803 .filter(UserRepoToPerm.repository == repo) \
793 804 .scalar()
794 805 if obj is None:
795 806 # create new !
796 807 obj = UserRepoToPerm()
797 808 obj.repository = repo
798 809 obj.user = user
799 810 obj.permission = permission
800 811 self.sa.add(obj)
801 812 log.debug('Granted perm %s to %s on %s', perm, user, repo)
802 813 action_logger_generic(
803 814 'granted permission: {} to user: {} on repo: {}'.format(
804 815 perm, user, repo), namespace='security.repo')
805 816 return obj
806 817
807 818 def revoke_user_permission(self, repo, user):
808 819 """
809 820 Revoke permission for user on given repository
810 821
811 822 :param repo: Instance of Repository, repository_id, or repository name
812 823 :param user: Instance of User, user_id or username
813 824 """
814 825
815 826 user = self._get_user(user)
816 827 repo = self._get_repo(repo)
817 828
818 829 obj = self.sa.query(UserRepoToPerm) \
819 830 .filter(UserRepoToPerm.repository == repo) \
820 831 .filter(UserRepoToPerm.user == user) \
821 832 .scalar()
822 833 if obj:
823 834 self.sa.delete(obj)
824 835 log.debug('Revoked perm on %s on %s', repo, user)
825 836 action_logger_generic(
826 837 'revoked permission from user: {} on repo: {}'.format(
827 838 user, repo), namespace='security.repo')
828 839
829 840 def grant_user_group_permission(self, repo, group_name, perm):
830 841 """
831 842 Grant permission for user group on given repository, or update
832 843 existing one if found
833 844
834 845 :param repo: Instance of Repository, repository_id, or repository name
835 846 :param group_name: Instance of UserGroup, users_group_id,
836 847 or user group name
837 848 :param perm: Instance of Permission, or permission_name
838 849 """
839 850 repo = self._get_repo(repo)
840 851 group_name = self._get_user_group(group_name)
841 852 permission = self._get_perm(perm)
842 853
843 854 # check if we have that permission already
844 855 obj = self.sa.query(UserGroupRepoToPerm) \
845 856 .filter(UserGroupRepoToPerm.users_group == group_name) \
846 857 .filter(UserGroupRepoToPerm.repository == repo) \
847 858 .scalar()
848 859
849 860 if obj is None:
850 861 # create new
851 862 obj = UserGroupRepoToPerm()
852 863
853 864 obj.repository = repo
854 865 obj.users_group = group_name
855 866 obj.permission = permission
856 867 self.sa.add(obj)
857 868 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
858 869 action_logger_generic(
859 870 'granted permission: {} to usergroup: {} on repo: {}'.format(
860 871 perm, group_name, repo), namespace='security.repo')
861 872
862 873 return obj
863 874
864 875 def revoke_user_group_permission(self, repo, group_name):
865 876 """
866 877 Revoke permission for user group on given repository
867 878
868 879 :param repo: Instance of Repository, repository_id, or repository name
869 880 :param group_name: Instance of UserGroup, users_group_id,
870 881 or user group name
871 882 """
872 883 repo = self._get_repo(repo)
873 884 group_name = self._get_user_group(group_name)
874 885
875 886 obj = self.sa.query(UserGroupRepoToPerm) \
876 887 .filter(UserGroupRepoToPerm.repository == repo) \
877 888 .filter(UserGroupRepoToPerm.users_group == group_name) \
878 889 .scalar()
879 890 if obj:
880 891 self.sa.delete(obj)
881 892 log.debug('Revoked perm to %s on %s', repo, group_name)
882 893 action_logger_generic(
883 894 'revoked permission from usergroup: {} on repo: {}'.format(
884 895 group_name, repo), namespace='security.repo')
885 896
886 897 def delete_stats(self, repo_name):
887 898 """
888 899 removes stats for given repo
889 900
890 901 :param repo_name:
891 902 """
892 903 repo = self._get_repo(repo_name)
893 904 try:
894 905 obj = self.sa.query(Statistics) \
895 906 .filter(Statistics.repository == repo).scalar()
896 907 if obj:
897 908 self.sa.delete(obj)
898 909 except Exception:
899 910 log.error(traceback.format_exc())
900 911 raise
901 912
902 913 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
903 914 field_type='str', field_desc=''):
904 915
905 916 repo = self._get_repo(repo_name)
906 917
907 918 new_field = RepositoryField()
908 919 new_field.repository = repo
909 920 new_field.field_key = field_key
910 921 new_field.field_type = field_type # python type
911 922 new_field.field_value = field_value
912 923 new_field.field_desc = field_desc
913 924 new_field.field_label = field_label
914 925 self.sa.add(new_field)
915 926 return new_field
916 927
917 928 def delete_repo_field(self, repo_name, field_key):
918 929 repo = self._get_repo(repo_name)
919 930 field = RepositoryField.get_by_key_name(field_key, repo)
920 931 if field:
921 932 self.sa.delete(field)
922 933
923 934 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
924 935 clone_uri=None, repo_store_location=None,
925 936 use_global_config=False, install_hooks=True):
926 937 """
927 938 makes repository on filesystem. It's group aware means it'll create
928 939 a repository within a group, and alter the paths accordingly of
929 940 group location
930 941
931 942 :param repo_name:
932 943 :param alias:
933 944 :param parent:
934 945 :param clone_uri:
935 946 :param repo_store_location:
936 947 """
937 948 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
938 949 from rhodecode.model.scm import ScmModel
939 950
940 951 if Repository.NAME_SEP in repo_name:
941 952 raise ValueError(
942 953 'repo_name must not contain groups got `%s`' % repo_name)
943 954
944 955 if isinstance(repo_group, RepoGroup):
945 956 new_parent_path = os.sep.join(repo_group.full_path_splitted)
946 957 else:
947 958 new_parent_path = repo_group or ''
948 959
949 960 if repo_store_location:
950 961 _paths = [repo_store_location]
951 962 else:
952 963 _paths = [self.repos_path, new_parent_path, repo_name]
953 964 # we need to make it str for mercurial
954 965 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
955 966
956 967 # check if this path is not a repository
957 968 if is_valid_repo(repo_path, self.repos_path):
958 969 raise Exception('This path %s is a valid repository' % repo_path)
959 970
960 971 # check if this path is a group
961 972 if is_valid_repo_group(repo_path, self.repos_path):
962 973 raise Exception('This path %s is a valid group' % repo_path)
963 974
964 975 log.info('creating repo %s in %s from url: `%s`',
965 976 repo_name, safe_unicode(repo_path),
966 977 obfuscate_url_pw(clone_uri))
967 978
968 979 backend = get_backend(repo_type)
969 980
970 981 config_repo = None if use_global_config else repo_name
971 982 if config_repo and new_parent_path:
972 983 config_repo = Repository.NAME_SEP.join(
973 984 (new_parent_path, config_repo))
974 985 config = make_db_config(clear_session=False, repo=config_repo)
975 986 config.set('extensions', 'largefiles', '')
976 987
977 988 # patch and reset hooks section of UI config to not run any
978 989 # hooks on creating remote repo
979 990 config.clear_section('hooks')
980 991
981 992 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
982 993 if repo_type == 'git':
983 994 repo = backend(
984 995 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
985 996 with_wire={"cache": False})
986 997 else:
987 998 repo = backend(
988 999 repo_path, config=config, create=True, src_url=clone_uri,
989 1000 with_wire={"cache": False})
990 1001
991 1002 if install_hooks:
992 1003 repo.install_hooks()
993 1004
994 1005 log.debug('Created repo %s with %s backend',
995 1006 safe_unicode(repo_name), safe_unicode(repo_type))
996 1007 return repo
997 1008
998 1009 def _rename_filesystem_repo(self, old, new):
999 1010 """
1000 1011 renames repository on filesystem
1001 1012
1002 1013 :param old: old name
1003 1014 :param new: new name
1004 1015 """
1005 1016 log.info('renaming repo from %s to %s', old, new)
1006 1017
1007 1018 old_path = os.path.join(self.repos_path, old)
1008 1019 new_path = os.path.join(self.repos_path, new)
1009 1020 if os.path.isdir(new_path):
1010 1021 raise Exception(
1011 1022 'Was trying to rename to already existing dir %s' % new_path
1012 1023 )
1013 1024 shutil.move(old_path, new_path)
1014 1025
1015 1026 def _delete_filesystem_repo(self, repo):
1016 1027 """
1017 1028 removes repo from filesystem, the removal is acctually made by
1018 1029 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1019 1030 repository is no longer valid for rhodecode, can be undeleted later on
1020 1031 by reverting the renames on this repository
1021 1032
1022 1033 :param repo: repo object
1023 1034 """
1024 1035 rm_path = os.path.join(self.repos_path, repo.repo_name)
1025 1036 repo_group = repo.group
1026 1037 log.info("Removing repository %s", rm_path)
1027 1038 # disable hg/git internal that it doesn't get detected as repo
1028 1039 alias = repo.repo_type
1029 1040
1030 1041 config = make_db_config(clear_session=False)
1031 1042 config.set('extensions', 'largefiles', '')
1032 1043 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1033 1044
1034 1045 # skip this for bare git repos
1035 1046 if not bare:
1036 1047 # disable VCS repo
1037 1048 vcs_path = os.path.join(rm_path, '.%s' % alias)
1038 1049 if os.path.exists(vcs_path):
1039 1050 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1040 1051
1041 1052 _now = datetime.datetime.now()
1042 1053 _ms = str(_now.microsecond).rjust(6, '0')
1043 1054 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1044 1055 repo.just_name)
1045 1056 if repo_group:
1046 1057 # if repository is in group, prefix the removal path with the group
1047 1058 args = repo_group.full_path_splitted + [_d]
1048 1059 _d = os.path.join(*args)
1049 1060
1050 1061 if os.path.isdir(rm_path):
1051 1062 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1052 1063
1053 1064 # finally cleanup diff-cache if it exists
1054 1065 cached_diffs_dir = repo.cached_diffs_dir
1055 1066 if os.path.isdir(cached_diffs_dir):
1056 1067 shutil.rmtree(cached_diffs_dir)
1057 1068
1058 1069
1059 1070 class ReadmeFinder:
1060 1071 """
1061 1072 Utility which knows how to find a readme for a specific commit.
1062 1073
1063 1074 The main idea is that this is a configurable algorithm. When creating an
1064 1075 instance you can define parameters, currently only the `default_renderer`.
1065 1076 Based on this configuration the method :meth:`search` behaves slightly
1066 1077 different.
1067 1078 """
1068 1079
1069 1080 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1070 1081 path_re = re.compile(r'^docs?', re.IGNORECASE)
1071 1082
1072 1083 default_priorities = {
1073 1084 None: 0,
1074 1085 '.text': 2,
1075 1086 '.txt': 3,
1076 1087 '.rst': 1,
1077 1088 '.rest': 2,
1078 1089 '.md': 1,
1079 1090 '.mkdn': 2,
1080 1091 '.mdown': 3,
1081 1092 '.markdown': 4,
1082 1093 }
1083 1094
1084 1095 path_priority = {
1085 1096 'doc': 0,
1086 1097 'docs': 1,
1087 1098 }
1088 1099
1089 1100 FALLBACK_PRIORITY = 99
1090 1101
1091 1102 RENDERER_TO_EXTENSION = {
1092 1103 'rst': ['.rst', '.rest'],
1093 1104 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1094 1105 }
1095 1106
1096 1107 def __init__(self, default_renderer=None):
1097 1108 self._default_renderer = default_renderer
1098 1109 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1099 1110 default_renderer, [])
1100 1111
1101 1112 def search(self, commit, path=u'/'):
1102 1113 """
1103 1114 Find a readme in the given `commit`.
1104 1115 """
1105 1116 nodes = commit.get_nodes(path)
1106 1117 matches = self._match_readmes(nodes)
1107 1118 matches = self._sort_according_to_priority(matches)
1108 1119 if matches:
1109 1120 return matches[0].node
1110 1121
1111 1122 paths = self._match_paths(nodes)
1112 1123 paths = self._sort_paths_according_to_priority(paths)
1113 1124 for path in paths:
1114 1125 match = self.search(commit, path=path)
1115 1126 if match:
1116 1127 return match
1117 1128
1118 1129 return None
1119 1130
1120 1131 def _match_readmes(self, nodes):
1121 1132 for node in nodes:
1122 1133 if not node.is_file():
1123 1134 continue
1124 1135 path = node.path.rsplit('/', 1)[-1]
1125 1136 match = self.readme_re.match(path)
1126 1137 if match:
1127 1138 extension = match.group(1)
1128 1139 yield ReadmeMatch(node, match, self._priority(extension))
1129 1140
1130 1141 def _match_paths(self, nodes):
1131 1142 for node in nodes:
1132 1143 if not node.is_dir():
1133 1144 continue
1134 1145 match = self.path_re.match(node.path)
1135 1146 if match:
1136 1147 yield node.path
1137 1148
1138 1149 def _priority(self, extension):
1139 1150 renderer_priority = (
1140 1151 0 if extension in self._renderer_extensions else 1)
1141 1152 extension_priority = self.default_priorities.get(
1142 1153 extension, self.FALLBACK_PRIORITY)
1143 1154 return (renderer_priority, extension_priority)
1144 1155
1145 1156 def _sort_according_to_priority(self, matches):
1146 1157
1147 1158 def priority_and_path(match):
1148 1159 return (match.priority, match.path)
1149 1160
1150 1161 return sorted(matches, key=priority_and_path)
1151 1162
1152 1163 def _sort_paths_according_to_priority(self, paths):
1153 1164
1154 1165 def priority_and_path(path):
1155 1166 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1156 1167
1157 1168 return sorted(paths, key=priority_and_path)
1158 1169
1159 1170
1160 1171 class ReadmeMatch:
1161 1172
1162 1173 def __init__(self, node, match, priority):
1163 1174 self.node = node
1164 1175 self._match = match
1165 1176 self.priority = priority
1166 1177
1167 1178 @property
1168 1179 def path(self):
1169 1180 return self.node.path
1170 1181
1171 1182 def __repr__(self):
1172 1183 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,887 +1,897 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 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 """
23 23 repo group model for RhodeCode
24 24 """
25 25
26 26 import os
27 27 import datetime
28 28 import itertools
29 29 import logging
30 30 import shutil
31 31 import time
32 32 import traceback
33 33 import string
34 34
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 37 from rhodecode import events
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 41 UserGroup, Repository)
42 from rhodecode.model.permission import PermissionModel
42 43 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 44 from rhodecode.lib.caching_query import FromCache
44 45 from rhodecode.lib.utils2 import action_logger_generic
45 46
46 47 log = logging.getLogger(__name__)
47 48
48 49
49 50 class RepoGroupModel(BaseModel):
50 51
51 52 cls = RepoGroup
52 53 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
53 54 PERSONAL_GROUP_PATTERN = '${username}' # default
54 55
55 56 def _get_user_group(self, users_group):
56 57 return self._get_instance(UserGroup, users_group,
57 58 callback=UserGroup.get_by_group_name)
58 59
59 60 def _get_repo_group(self, repo_group):
60 61 return self._get_instance(RepoGroup, repo_group,
61 62 callback=RepoGroup.get_by_group_name)
62 63
63 64 def get_repo_group(self, repo_group):
64 65 return self._get_repo_group(repo_group)
65 66
66 67 @LazyProperty
67 68 def repos_path(self):
68 69 """
69 70 Gets the repositories root path from database
70 71 """
71 72
72 73 settings_model = VcsSettingsModel(sa=self.sa)
73 74 return settings_model.get_repos_location()
74 75
75 76 def get_by_group_name(self, repo_group_name, cache=None):
76 77 repo = self.sa.query(RepoGroup) \
77 78 .filter(RepoGroup.group_name == repo_group_name)
78 79
79 80 if cache:
80 81 name_key = _hash_key(repo_group_name)
81 82 repo = repo.options(
82 83 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
83 84 return repo.scalar()
84 85
85 86 def get_default_create_personal_repo_group(self):
86 87 value = SettingsModel().get_setting_by_name(
87 88 'create_personal_repo_group')
88 89 return value.app_settings_value if value else None or False
89 90
90 91 def get_personal_group_name_pattern(self):
91 92 value = SettingsModel().get_setting_by_name(
92 93 'personal_repo_group_pattern')
93 94 val = value.app_settings_value if value else None
94 95 group_template = val or self.PERSONAL_GROUP_PATTERN
95 96
96 97 group_template = group_template.lstrip('/')
97 98 return group_template
98 99
99 100 def get_personal_group_name(self, user):
100 101 template = self.get_personal_group_name_pattern()
101 102 return string.Template(template).safe_substitute(
102 103 username=user.username,
103 104 user_id=user.user_id,
104 105 first_name=user.first_name,
105 106 last_name=user.last_name,
106 107 )
107 108
108 109 def create_personal_repo_group(self, user, commit_early=True):
109 110 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
110 111 personal_repo_group_name = self.get_personal_group_name(user)
111 112
112 113 # create a new one
113 114 RepoGroupModel().create(
114 115 group_name=personal_repo_group_name,
115 116 group_description=desc,
116 117 owner=user.username,
117 118 personal=True,
118 119 commit_early=commit_early)
119 120
120 121 def _create_default_perms(self, new_group):
121 122 # create default permission
122 123 default_perm = 'group.read'
123 124 def_user = User.get_default_user()
124 125 for p in def_user.user_perms:
125 126 if p.permission.permission_name.startswith('group.'):
126 127 default_perm = p.permission.permission_name
127 128 break
128 129
129 130 repo_group_to_perm = UserRepoGroupToPerm()
130 131 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
131 132
132 133 repo_group_to_perm.group = new_group
133 134 repo_group_to_perm.user_id = def_user.user_id
134 135 return repo_group_to_perm
135 136
136 137 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
137 138 get_object=False):
138 139 """
139 140 Get's the group name and a parent group name from given group name.
140 141 If repo_in_path is set to truth, we asume the full path also includes
141 142 repo name, in such case we clean the last element.
142 143
143 144 :param group_name_full:
144 145 """
145 146 split_paths = 1
146 147 if repo_in_path:
147 148 split_paths = 2
148 149 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
149 150
150 151 if repo_in_path and len(_parts) > 1:
151 152 # such case last element is the repo_name
152 153 _parts.pop(-1)
153 154 group_name_cleaned = _parts[-1] # just the group name
154 155 parent_repo_group_name = None
155 156
156 157 if len(_parts) > 1:
157 158 parent_repo_group_name = _parts[0]
158 159
159 160 parent_group = None
160 161 if parent_repo_group_name:
161 162 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
162 163
163 164 if get_object:
164 165 return group_name_cleaned, parent_repo_group_name, parent_group
165 166
166 167 return group_name_cleaned, parent_repo_group_name
167 168
168 169 def check_exist_filesystem(self, group_name, exc_on_failure=True):
169 170 create_path = os.path.join(self.repos_path, group_name)
170 171 log.debug('creating new group in %s', create_path)
171 172
172 173 if os.path.isdir(create_path):
173 174 if exc_on_failure:
174 175 abs_create_path = os.path.abspath(create_path)
175 176 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
176 177 return False
177 178 return True
178 179
179 180 def _create_group(self, group_name):
180 181 """
181 182 makes repository group on filesystem
182 183
183 184 :param repo_name:
184 185 :param parent_id:
185 186 """
186 187
187 188 self.check_exist_filesystem(group_name)
188 189 create_path = os.path.join(self.repos_path, group_name)
189 190 log.debug('creating new group in %s', create_path)
190 191 os.makedirs(create_path, mode=0o755)
191 192 log.debug('created group in %s', create_path)
192 193
193 194 def _rename_group(self, old, new):
194 195 """
195 196 Renames a group on filesystem
196 197
197 198 :param group_name:
198 199 """
199 200
200 201 if old == new:
201 202 log.debug('skipping group rename')
202 203 return
203 204
204 205 log.debug('renaming repository group from %s to %s', old, new)
205 206
206 207 old_path = os.path.join(self.repos_path, old)
207 208 new_path = os.path.join(self.repos_path, new)
208 209
209 210 log.debug('renaming repos paths from %s to %s', old_path, new_path)
210 211
211 212 if os.path.isdir(new_path):
212 213 raise Exception('Was trying to rename to already '
213 214 'existing dir %s' % new_path)
214 215 shutil.move(old_path, new_path)
215 216
216 217 def _delete_filesystem_group(self, group, force_delete=False):
217 218 """
218 219 Deletes a group from a filesystem
219 220
220 221 :param group: instance of group from database
221 222 :param force_delete: use shutil rmtree to remove all objects
222 223 """
223 224 paths = group.full_path.split(RepoGroup.url_sep())
224 225 paths = os.sep.join(paths)
225 226
226 227 rm_path = os.path.join(self.repos_path, paths)
227 228 log.info("Removing group %s", rm_path)
228 229 # delete only if that path really exists
229 230 if os.path.isdir(rm_path):
230 231 if force_delete:
231 232 shutil.rmtree(rm_path)
232 233 else:
233 234 # archive that group`
234 235 _now = datetime.datetime.now()
235 236 _ms = str(_now.microsecond).rjust(6, '0')
236 237 _d = 'rm__%s_GROUP_%s' % (
237 238 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
238 239 shutil.move(rm_path, os.path.join(self.repos_path, _d))
239 240
240 241 def create(self, group_name, group_description, owner, just_db=False,
241 242 copy_permissions=False, personal=None, commit_early=True):
242 243
243 244 (group_name_cleaned,
244 245 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
245 246
246 247 parent_group = None
247 248 if parent_group_name:
248 249 parent_group = self._get_repo_group(parent_group_name)
249 250 if not parent_group:
250 251 # we tried to create a nested group, but the parent is not
251 252 # existing
252 253 raise ValueError(
253 254 'Parent group `%s` given in `%s` group name '
254 255 'is not yet existing.' % (parent_group_name, group_name))
255 256
256 257 # because we are doing a cleanup, we need to check if such directory
257 258 # already exists. If we don't do that we can accidentally delete
258 259 # existing directory via cleanup that can cause data issues, since
259 260 # delete does a folder rename to special syntax later cleanup
260 261 # functions can delete this
261 262 cleanup_group = self.check_exist_filesystem(group_name,
262 263 exc_on_failure=False)
263 264 user = self._get_user(owner)
264 265 if not user:
265 266 raise ValueError('Owner %s not found as rhodecode user', owner)
266 267
267 268 try:
268 269 new_repo_group = RepoGroup()
269 270 new_repo_group.user = user
270 271 new_repo_group.group_description = group_description or group_name
271 272 new_repo_group.parent_group = parent_group
272 273 new_repo_group.group_name = group_name
273 274 new_repo_group.personal = personal
274 275
275 276 self.sa.add(new_repo_group)
276 277
277 278 # create an ADMIN permission for owner except if we're super admin,
278 279 # later owner should go into the owner field of groups
279 280 if not user.is_admin:
280 281 self.grant_user_permission(repo_group=new_repo_group,
281 282 user=owner, perm='group.admin')
282 283
283 284 if parent_group and copy_permissions:
284 285 # copy permissions from parent
285 286 user_perms = UserRepoGroupToPerm.query() \
286 287 .filter(UserRepoGroupToPerm.group == parent_group).all()
287 288
288 289 group_perms = UserGroupRepoGroupToPerm.query() \
289 290 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
290 291
291 292 for perm in user_perms:
292 293 # don't copy over the permission for user who is creating
293 294 # this group, if he is not super admin he get's admin
294 295 # permission set above
295 296 if perm.user != user or user.is_admin:
296 297 UserRepoGroupToPerm.create(
297 298 perm.user, new_repo_group, perm.permission)
298 299
299 300 for perm in group_perms:
300 301 UserGroupRepoGroupToPerm.create(
301 302 perm.users_group, new_repo_group, perm.permission)
302 303 else:
303 304 perm_obj = self._create_default_perms(new_repo_group)
304 305 self.sa.add(perm_obj)
305 306
306 307 # now commit the changes, earlier so we are sure everything is in
307 308 # the database.
308 309 if commit_early:
309 310 self.sa.commit()
310 311 if not just_db:
311 312 self._create_group(new_repo_group.group_name)
312 313
313 314 # trigger the post hook
314 315 from rhodecode.lib import hooks_base
315 316 repo_group = RepoGroup.get_by_group_name(group_name)
316 317
317 318 # update repo group commit caches initially
318 319 repo_group.update_commit_cache()
319 320
320 321 hooks_base.create_repository_group(
321 322 created_by=user.username, **repo_group.get_dict())
322 323
323 324 # Trigger create event.
324 325 events.trigger(events.RepoGroupCreateEvent(repo_group))
325 326
326 327 return new_repo_group
327 328 except Exception:
328 329 self.sa.rollback()
329 330 log.exception('Exception occurred when creating repository group, '
330 331 'doing cleanup...')
331 332 # rollback things manually !
332 333 repo_group = RepoGroup.get_by_group_name(group_name)
333 334 if repo_group:
334 335 RepoGroup.delete(repo_group.group_id)
335 336 self.sa.commit()
336 337 if cleanup_group:
337 338 RepoGroupModel()._delete_filesystem_group(repo_group)
338 339 raise
339 340
340 341 def update_permissions(
341 342 self, repo_group, perm_additions=None, perm_updates=None,
342 343 perm_deletions=None, recursive=None, check_perms=True,
343 344 cur_user=None):
344 345 from rhodecode.model.repo import RepoModel
345 346 from rhodecode.lib.auth import HasUserGroupPermissionAny
346 347
347 348 if not perm_additions:
348 349 perm_additions = []
349 350 if not perm_updates:
350 351 perm_updates = []
351 352 if not perm_deletions:
352 353 perm_deletions = []
353 354
354 355 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
355 356
356 357 changes = {
357 358 'added': [],
358 359 'updated': [],
359 360 'deleted': [],
360 361 'default_user_changed': None
361 362 }
362 363
363 364 def _set_perm_user(obj, user, perm):
364 365 if isinstance(obj, RepoGroup):
365 366 self.grant_user_permission(
366 367 repo_group=obj, user=user, perm=perm)
367 368 elif isinstance(obj, Repository):
368 369 # private repos will not allow to change the default
369 370 # permissions using recursive mode
370 371 if obj.private and user == User.DEFAULT_USER:
371 372 return
372 373
373 374 # we set group permission but we have to switch to repo
374 375 # permission
375 376 perm = perm.replace('group.', 'repository.')
376 377 RepoModel().grant_user_permission(
377 378 repo=obj, user=user, perm=perm)
378 379
379 380 def _set_perm_group(obj, users_group, perm):
380 381 if isinstance(obj, RepoGroup):
381 382 self.grant_user_group_permission(
382 383 repo_group=obj, group_name=users_group, perm=perm)
383 384 elif isinstance(obj, Repository):
384 385 # we set group permission but we have to switch to repo
385 386 # permission
386 387 perm = perm.replace('group.', 'repository.')
387 388 RepoModel().grant_user_group_permission(
388 389 repo=obj, group_name=users_group, perm=perm)
389 390
390 391 def _revoke_perm_user(obj, user):
391 392 if isinstance(obj, RepoGroup):
392 393 self.revoke_user_permission(repo_group=obj, user=user)
393 394 elif isinstance(obj, Repository):
394 395 RepoModel().revoke_user_permission(repo=obj, user=user)
395 396
396 397 def _revoke_perm_group(obj, user_group):
397 398 if isinstance(obj, RepoGroup):
398 399 self.revoke_user_group_permission(
399 400 repo_group=obj, group_name=user_group)
400 401 elif isinstance(obj, Repository):
401 402 RepoModel().revoke_user_group_permission(
402 403 repo=obj, group_name=user_group)
403 404
404 405 # start updates
405 406 log.debug('Now updating permissions for %s in recursive mode:%s',
406 407 repo_group, recursive)
407 408
408 409 # initialize check function, we'll call that multiple times
409 410 has_group_perm = HasUserGroupPermissionAny(*req_perms)
410 411
411 412 for obj in repo_group.recursive_groups_and_repos():
412 413 # iterated obj is an instance of a repos group or repository in
413 414 # that group, recursive option can be: none, repos, groups, all
414 415 if recursive == 'all':
415 416 obj = obj
416 417 elif recursive == 'repos':
417 418 # skip groups, other than this one
418 419 if isinstance(obj, RepoGroup) and not obj == repo_group:
419 420 continue
420 421 elif recursive == 'groups':
421 422 # skip repos
422 423 if isinstance(obj, Repository):
423 424 continue
424 425 else: # recursive == 'none':
425 426 # DEFAULT option - don't apply to iterated objects
426 427 # also we do a break at the end of this loop. if we are not
427 428 # in recursive mode
428 429 obj = repo_group
429 430
430 431 change_obj = obj.get_api_data()
431 432
432 433 # update permissions
433 434 for member_id, perm, member_type in perm_updates:
434 435 member_id = int(member_id)
435 436 if member_type == 'user':
436 437 member_name = User.get(member_id).username
437 438 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
438 439 # NOTE(dan): detect if we changed permissions for default user
439 440 perm_obj = self.sa.query(UserRepoGroupToPerm) \
440 441 .filter(UserRepoGroupToPerm.user_id == member_id) \
441 442 .filter(UserRepoGroupToPerm.group == repo_group) \
442 443 .scalar()
443 444 if perm_obj and perm_obj.permission.permission_name != perm:
444 445 changes['default_user_changed'] = True
445 446
446 447 # this updates also current one if found
447 448 _set_perm_user(obj, user=member_id, perm=perm)
448 449 elif member_type == 'user_group':
449 450 member_name = UserGroup.get(member_id).users_group_name
450 451 if not check_perms or has_group_perm(member_name,
451 452 user=cur_user):
452 453 _set_perm_group(obj, users_group=member_id, perm=perm)
453 454 else:
454 455 raise ValueError("member_type must be 'user' or 'user_group' "
455 456 "got {} instead".format(member_type))
456 457
457 458 changes['updated'].append(
458 459 {'change_obj': change_obj, 'type': member_type,
459 460 'id': member_id, 'name': member_name, 'new_perm': perm})
460 461
461 462 # set new permissions
462 463 for member_id, perm, member_type in perm_additions:
463 464 member_id = int(member_id)
464 465 if member_type == 'user':
465 466 member_name = User.get(member_id).username
466 467 _set_perm_user(obj, user=member_id, perm=perm)
467 468 elif member_type == 'user_group':
468 469 # check if we have permissions to alter this usergroup
469 470 member_name = UserGroup.get(member_id).users_group_name
470 471 if not check_perms or has_group_perm(member_name,
471 472 user=cur_user):
472 473 _set_perm_group(obj, users_group=member_id, perm=perm)
473 474 else:
474 475 raise ValueError("member_type must be 'user' or 'user_group' "
475 476 "got {} instead".format(member_type))
476 477
477 478 changes['added'].append(
478 479 {'change_obj': change_obj, 'type': member_type,
479 480 'id': member_id, 'name': member_name, 'new_perm': perm})
480 481
481 482 # delete permissions
482 483 for member_id, perm, member_type in perm_deletions:
483 484 member_id = int(member_id)
484 485 if member_type == 'user':
485 486 member_name = User.get(member_id).username
486 487 _revoke_perm_user(obj, user=member_id)
487 488 elif member_type == 'user_group':
488 489 # check if we have permissions to alter this usergroup
489 490 member_name = UserGroup.get(member_id).users_group_name
490 491 if not check_perms or has_group_perm(member_name,
491 492 user=cur_user):
492 493 _revoke_perm_group(obj, user_group=member_id)
493 494 else:
494 495 raise ValueError("member_type must be 'user' or 'user_group' "
495 496 "got {} instead".format(member_type))
496 497
497 498 changes['deleted'].append(
498 499 {'change_obj': change_obj, 'type': member_type,
499 500 'id': member_id, 'name': member_name, 'new_perm': perm})
500 501
501 502 # if it's not recursive call for all,repos,groups
502 503 # break the loop and don't proceed with other changes
503 504 if recursive not in ['all', 'repos', 'groups']:
504 505 break
505 506
506 507 return changes
507 508
508 509 def update(self, repo_group, form_data):
509 510 try:
510 511 repo_group = self._get_repo_group(repo_group)
511 512 old_path = repo_group.full_path
512 513
513 514 # change properties
514 515 if 'group_description' in form_data:
515 516 repo_group.group_description = form_data['group_description']
516 517
517 518 if 'enable_locking' in form_data:
518 519 repo_group.enable_locking = form_data['enable_locking']
519 520
520 521 if 'group_parent_id' in form_data:
521 522 parent_group = (
522 523 self._get_repo_group(form_data['group_parent_id']))
523 524 repo_group.group_parent_id = (
524 525 parent_group.group_id if parent_group else None)
525 526 repo_group.parent_group = parent_group
526 527
527 528 # mikhail: to update the full_path, we have to explicitly
528 529 # update group_name
529 530 group_name = form_data.get('group_name', repo_group.name)
530 531 repo_group.group_name = repo_group.get_new_name(group_name)
531 532
532 533 new_path = repo_group.full_path
533 534
535 affected_user_ids = []
534 536 if 'user' in form_data:
535 repo_group.user = User.get_by_username(form_data['user'])
537 old_owner_id = repo_group.user.user_id
538 new_owner = User.get_by_username(form_data['user'])
539 repo_group.user = new_owner
540
541 if old_owner_id != new_owner.user_id:
542 affected_user_ids = [new_owner.user_id, old_owner_id]
536 543
537 544 self.sa.add(repo_group)
538 545
539 546 # iterate over all members of this groups and do fixes
540 547 # set locking if given
541 548 # if obj is a repoGroup also fix the name of the group according
542 549 # to the parent
543 550 # if obj is a Repo fix it's name
544 551 # this can be potentially heavy operation
545 552 for obj in repo_group.recursive_groups_and_repos():
546 553 # set the value from it's parent
547 554 obj.enable_locking = repo_group.enable_locking
548 555 if isinstance(obj, RepoGroup):
549 556 new_name = obj.get_new_name(obj.name)
550 557 log.debug('Fixing group %s to new name %s',
551 558 obj.group_name, new_name)
552 559 obj.group_name = new_name
553 560
554 561 elif isinstance(obj, Repository):
555 562 # we need to get all repositories from this new group and
556 563 # rename them accordingly to new group path
557 564 new_name = obj.get_new_name(obj.just_name)
558 565 log.debug('Fixing repo %s to new name %s',
559 566 obj.repo_name, new_name)
560 567 obj.repo_name = new_name
561 568
562 569 self.sa.add(obj)
563 570
564 571 self._rename_group(old_path, new_path)
565 572
566 573 # Trigger update event.
567 574 events.trigger(events.RepoGroupUpdateEvent(repo_group))
568 575
576 if affected_user_ids:
577 PermissionModel().trigger_permission_flush(affected_user_ids)
578
569 579 return repo_group
570 580 except Exception:
571 581 log.error(traceback.format_exc())
572 582 raise
573 583
574 584 def delete(self, repo_group, force_delete=False, fs_remove=True):
575 585 repo_group = self._get_repo_group(repo_group)
576 586 if not repo_group:
577 587 return False
578 588 try:
579 589 self.sa.delete(repo_group)
580 590 if fs_remove:
581 591 self._delete_filesystem_group(repo_group, force_delete)
582 592 else:
583 593 log.debug('skipping removal from filesystem')
584 594
585 595 # Trigger delete event.
586 596 events.trigger(events.RepoGroupDeleteEvent(repo_group))
587 597 return True
588 598
589 599 except Exception:
590 600 log.error('Error removing repo_group %s', repo_group)
591 601 raise
592 602
593 603 def grant_user_permission(self, repo_group, user, perm):
594 604 """
595 605 Grant permission for user on given repository group, or update
596 606 existing one if found
597 607
598 608 :param repo_group: Instance of RepoGroup, repositories_group_id,
599 609 or repositories_group name
600 610 :param user: Instance of User, user_id or username
601 611 :param perm: Instance of Permission, or permission_name
602 612 """
603 613
604 614 repo_group = self._get_repo_group(repo_group)
605 615 user = self._get_user(user)
606 616 permission = self._get_perm(perm)
607 617
608 618 # check if we have that permission already
609 619 obj = self.sa.query(UserRepoGroupToPerm)\
610 620 .filter(UserRepoGroupToPerm.user == user)\
611 621 .filter(UserRepoGroupToPerm.group == repo_group)\
612 622 .scalar()
613 623 if obj is None:
614 624 # create new !
615 625 obj = UserRepoGroupToPerm()
616 626 obj.group = repo_group
617 627 obj.user = user
618 628 obj.permission = permission
619 629 self.sa.add(obj)
620 630 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
621 631 action_logger_generic(
622 632 'granted permission: {} to user: {} on repogroup: {}'.format(
623 633 perm, user, repo_group), namespace='security.repogroup')
624 634 return obj
625 635
626 636 def revoke_user_permission(self, repo_group, user):
627 637 """
628 638 Revoke permission for user on given repository group
629 639
630 640 :param repo_group: Instance of RepoGroup, repositories_group_id,
631 641 or repositories_group name
632 642 :param user: Instance of User, user_id or username
633 643 """
634 644
635 645 repo_group = self._get_repo_group(repo_group)
636 646 user = self._get_user(user)
637 647
638 648 obj = self.sa.query(UserRepoGroupToPerm)\
639 649 .filter(UserRepoGroupToPerm.user == user)\
640 650 .filter(UserRepoGroupToPerm.group == repo_group)\
641 651 .scalar()
642 652 if obj:
643 653 self.sa.delete(obj)
644 654 log.debug('Revoked perm on %s on %s', repo_group, user)
645 655 action_logger_generic(
646 656 'revoked permission from user: {} on repogroup: {}'.format(
647 657 user, repo_group), namespace='security.repogroup')
648 658
649 659 def grant_user_group_permission(self, repo_group, group_name, perm):
650 660 """
651 661 Grant permission for user group on given repository group, or update
652 662 existing one if found
653 663
654 664 :param repo_group: Instance of RepoGroup, repositories_group_id,
655 665 or repositories_group name
656 666 :param group_name: Instance of UserGroup, users_group_id,
657 667 or user group name
658 668 :param perm: Instance of Permission, or permission_name
659 669 """
660 670 repo_group = self._get_repo_group(repo_group)
661 671 group_name = self._get_user_group(group_name)
662 672 permission = self._get_perm(perm)
663 673
664 674 # check if we have that permission already
665 675 obj = self.sa.query(UserGroupRepoGroupToPerm)\
666 676 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
667 677 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
668 678 .scalar()
669 679
670 680 if obj is None:
671 681 # create new
672 682 obj = UserGroupRepoGroupToPerm()
673 683
674 684 obj.group = repo_group
675 685 obj.users_group = group_name
676 686 obj.permission = permission
677 687 self.sa.add(obj)
678 688 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
679 689 action_logger_generic(
680 690 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
681 691 perm, group_name, repo_group), namespace='security.repogroup')
682 692 return obj
683 693
684 694 def revoke_user_group_permission(self, repo_group, group_name):
685 695 """
686 696 Revoke permission for user group on given repository group
687 697
688 698 :param repo_group: Instance of RepoGroup, repositories_group_id,
689 699 or repositories_group name
690 700 :param group_name: Instance of UserGroup, users_group_id,
691 701 or user group name
692 702 """
693 703 repo_group = self._get_repo_group(repo_group)
694 704 group_name = self._get_user_group(group_name)
695 705
696 706 obj = self.sa.query(UserGroupRepoGroupToPerm)\
697 707 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
698 708 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
699 709 .scalar()
700 710 if obj:
701 711 self.sa.delete(obj)
702 712 log.debug('Revoked perm to %s on %s', repo_group, group_name)
703 713 action_logger_generic(
704 714 'revoked permission from usergroup: {} on repogroup: {}'.format(
705 715 group_name, repo_group), namespace='security.repogroup')
706 716
707 717 @classmethod
708 718 def update_commit_cache(cls, repo_groups=None):
709 719 if not repo_groups:
710 720 repo_groups = RepoGroup.getAll()
711 721 for repo_group in repo_groups:
712 722 repo_group.update_commit_cache()
713 723
714 724 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
715 725 super_user_actions=False):
716 726
717 727 from pyramid.threadlocal import get_current_request
718 728 _render = get_current_request().get_partial_renderer(
719 729 'rhodecode:templates/data_table/_dt_elements.mako')
720 730 c = _render.get_call_context()
721 731 h = _render.get_helpers()
722 732
723 733 def quick_menu(repo_group_name):
724 734 return _render('quick_repo_group_menu', repo_group_name)
725 735
726 736 def repo_group_lnk(repo_group_name):
727 737 return _render('repo_group_name', repo_group_name)
728 738
729 739 def last_change(last_change):
730 740 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
731 741 ts = time.time()
732 742 utc_offset = (datetime.datetime.fromtimestamp(ts)
733 743 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
734 744 last_change = last_change + datetime.timedelta(seconds=utc_offset)
735 745 return _render("last_change", last_change)
736 746
737 747 def desc(desc, personal):
738 748 return _render(
739 749 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
740 750
741 751 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
742 752 return _render(
743 753 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
744 754
745 755 def repo_group_name(repo_group_name, children_groups):
746 756 return _render("repo_group_name", repo_group_name, children_groups)
747 757
748 758 def user_profile(username):
749 759 return _render('user_profile', username)
750 760
751 761 repo_group_data = []
752 762 for group in repo_group_list:
753 763 # NOTE(marcink): because we use only raw column we need to load it like that
754 764 changeset_cache = RepoGroup._load_changeset_cache(
755 765 '', group._changeset_cache)
756 766 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
757 767 row = {
758 768 "menu": quick_menu(group.group_name),
759 769 "name": repo_group_lnk(group.group_name),
760 770 "name_raw": group.group_name,
761 771
762 772 "last_change": last_change(last_commit_change),
763 773
764 774 "last_changeset": "",
765 775 "last_changeset_raw": "",
766 776
767 777 "desc": desc(h.escape(group.group_description), group.personal),
768 778 "top_level_repos": 0,
769 779 "owner": user_profile(group.User.username)
770 780 }
771 781 if admin:
772 782 repo_count = group.repositories.count()
773 783 children_groups = map(
774 784 h.safe_unicode,
775 785 itertools.chain((g.name for g in group.parents),
776 786 (x.name for x in [group])))
777 787 row.update({
778 788 "action": repo_group_actions(
779 789 group.group_id, group.group_name, repo_count),
780 790 "top_level_repos": repo_count,
781 791 "name": repo_group_name(group.group_name, children_groups),
782 792
783 793 })
784 794 repo_group_data.append(row)
785 795
786 796 return repo_group_data
787 797
788 798 def get_repo_groups_data_table(
789 799 self, draw, start, limit,
790 800 search_q, order_by, order_dir,
791 801 auth_user, repo_group_id):
792 802 from rhodecode.model.scm import RepoGroupList
793 803
794 804 _perms = ['group.read', 'group.write', 'group.admin']
795 805 repo_groups = RepoGroup.query() \
796 806 .filter(RepoGroup.group_parent_id == repo_group_id) \
797 807 .all()
798 808 auth_repo_group_list = RepoGroupList(
799 809 repo_groups, perm_set=_perms,
800 810 extra_kwargs=dict(user=auth_user))
801 811
802 812 allowed_ids = [-1]
803 813 for repo_group in auth_repo_group_list:
804 814 allowed_ids.append(repo_group.group_id)
805 815
806 816 repo_groups_data_total_count = RepoGroup.query() \
807 817 .filter(RepoGroup.group_parent_id == repo_group_id) \
808 818 .filter(or_(
809 819 # generate multiple IN to fix limitation problems
810 820 *in_filter_generator(RepoGroup.group_id, allowed_ids))
811 821 ) \
812 822 .count()
813 823
814 824 base_q = Session.query(
815 825 RepoGroup.group_name,
816 826 RepoGroup.group_name_hash,
817 827 RepoGroup.group_description,
818 828 RepoGroup.group_id,
819 829 RepoGroup.personal,
820 830 RepoGroup.updated_on,
821 831 RepoGroup._changeset_cache,
822 832 User,
823 833 ) \
824 834 .filter(RepoGroup.group_parent_id == repo_group_id) \
825 835 .filter(or_(
826 836 # generate multiple IN to fix limitation problems
827 837 *in_filter_generator(RepoGroup.group_id, allowed_ids))
828 838 ) \
829 839 .join(User, User.user_id == RepoGroup.user_id) \
830 840 .group_by(RepoGroup, User)
831 841
832 842 repo_groups_data_total_filtered_count = base_q.count()
833 843
834 844 sort_defined = False
835 845
836 846 if order_by == 'group_name':
837 847 sort_col = func.lower(RepoGroup.group_name)
838 848 sort_defined = True
839 849 elif order_by == 'user_username':
840 850 sort_col = User.username
841 851 else:
842 852 sort_col = getattr(RepoGroup, order_by, None)
843 853
844 854 if sort_defined or sort_col:
845 855 if order_dir == 'asc':
846 856 sort_col = sort_col.asc()
847 857 else:
848 858 sort_col = sort_col.desc()
849 859
850 860 base_q = base_q.order_by(sort_col)
851 861 base_q = base_q.offset(start).limit(limit)
852 862
853 863 repo_group_list = base_q.all()
854 864
855 865 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
856 866 repo_group_list=repo_group_list, admin=False)
857 867
858 868 data = ({
859 869 'draw': draw,
860 870 'data': repo_groups_data,
861 871 'recordsTotal': repo_groups_data_total_count,
862 872 'recordsFiltered': repo_groups_data_total_filtered_count,
863 873 })
864 874 return data
865 875
866 876 def _get_defaults(self, repo_group_name):
867 877 repo_group = RepoGroup.get_by_group_name(repo_group_name)
868 878
869 879 if repo_group is None:
870 880 return None
871 881
872 882 defaults = repo_group.get_dict()
873 883 defaults['repo_group_name'] = repo_group.name
874 884 defaults['repo_group_description'] = repo_group.group_description
875 885 defaults['repo_group_enable_locking'] = repo_group.enable_locking
876 886
877 887 # we use -1 as this is how in HTML, we mark an empty group
878 888 defaults['repo_group'] = defaults['group_parent_id'] or -1
879 889
880 890 # fill owner
881 891 if repo_group.user:
882 892 defaults.update({'user': repo_group.user.username})
883 893 else:
884 894 replacement_user = User.get_first_super_admin().username
885 895 defaults.update({'user': replacement_user})
886 896
887 897 return defaults
General Comments 0
You need to be logged in to leave comments. Login now