repo_group.py
886 lines
| 34.9 KiB
| text/x-python
|
PythonLexer
r5088 | # Copyright (C) 2011-2023 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
""" | ||||
repo group model for RhodeCode | ||||
""" | ||||
r1094 | import os | |||
r1 | import datetime | |||
import itertools | ||||
import logging | ||||
import shutil | ||||
r4000 | import time | |||
r1 | import traceback | |||
r1094 | import string | |||
r1 | ||||
from zope.cachedescriptors.property import Lazy as LazyProperty | ||||
Martin Bornhold
|
r557 | from rhodecode import events | ||
r1 | from rhodecode.model import BaseModel | |||
r4147 | from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator, | |||
Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm, | ||||
r1 | UserGroup, Repository) | |||
r4662 | from rhodecode.model.permission import PermissionModel | |||
r1094 | from rhodecode.model.settings import VcsSettingsModel, SettingsModel | |||
r1 | from rhodecode.lib.caching_query import FromCache | |||
r4223 | from rhodecode.lib.utils2 import action_logger_generic | |||
r1 | ||||
log = logging.getLogger(__name__) | ||||
class RepoGroupModel(BaseModel): | ||||
cls = RepoGroup | ||||
r1102 | PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`' | |||
r1094 | PERSONAL_GROUP_PATTERN = '${username}' # default | |||
r1 | ||||
def _get_user_group(self, users_group): | ||||
return self._get_instance(UserGroup, users_group, | ||||
callback=UserGroup.get_by_group_name) | ||||
def _get_repo_group(self, repo_group): | ||||
return self._get_instance(RepoGroup, repo_group, | ||||
callback=RepoGroup.get_by_group_name) | ||||
r4611 | def get_repo_group(self, repo_group): | |||
return self._get_repo_group(repo_group) | ||||
r1 | def get_by_group_name(self, repo_group_name, cache=None): | |||
repo = self.sa.query(RepoGroup) \ | ||||
.filter(RepoGroup.group_name == repo_group_name) | ||||
if cache: | ||||
r1749 | name_key = _hash_key(repo_group_name) | |||
repo = repo.options( | ||||
r5009 | FromCache("sql_cache_short", f"get_repo_group_{name_key}")) | |||
r1 | return repo.scalar() | |||
r1094 | def get_default_create_personal_repo_group(self): | |||
value = SettingsModel().get_setting_by_name( | ||||
'create_personal_repo_group') | ||||
return value.app_settings_value if value else None or False | ||||
def get_personal_group_name_pattern(self): | ||||
value = SettingsModel().get_setting_by_name( | ||||
'personal_repo_group_pattern') | ||||
val = value.app_settings_value if value else None | ||||
group_template = val or self.PERSONAL_GROUP_PATTERN | ||||
group_template = group_template.lstrip('/') | ||||
return group_template | ||||
def get_personal_group_name(self, user): | ||||
template = self.get_personal_group_name_pattern() | ||||
return string.Template(template).safe_substitute( | ||||
username=user.username, | ||||
user_id=user.user_id, | ||||
r3659 | first_name=user.first_name, | |||
last_name=user.last_name, | ||||
r1094 | ) | |||
def create_personal_repo_group(self, user, commit_early=True): | ||||
desc = self.PERSONAL_GROUP_DESC % {'username': user.username} | ||||
personal_repo_group_name = self.get_personal_group_name(user) | ||||
# create a new one | ||||
RepoGroupModel().create( | ||||
group_name=personal_repo_group_name, | ||||
group_description=desc, | ||||
owner=user.username, | ||||
personal=True, | ||||
commit_early=commit_early) | ||||
r1 | def _create_default_perms(self, new_group): | |||
# create default permission | ||||
default_perm = 'group.read' | ||||
def_user = User.get_default_user() | ||||
for p in def_user.user_perms: | ||||
if p.permission.permission_name.startswith('group.'): | ||||
default_perm = p.permission.permission_name | ||||
break | ||||
repo_group_to_perm = UserRepoGroupToPerm() | ||||
repo_group_to_perm.permission = Permission.get_by_key(default_perm) | ||||
repo_group_to_perm.group = new_group | ||||
r5070 | repo_group_to_perm.user = def_user | |||
r1 | return repo_group_to_perm | |||
r1151 | def _get_group_name_and_parent(self, group_name_full, repo_in_path=False, | |||
get_object=False): | ||||
r1 | """ | |||
Get's the group name and a parent group name from given group name. | ||||
If repo_in_path is set to truth, we asume the full path also includes | ||||
repo name, in such case we clean the last element. | ||||
:param group_name_full: | ||||
""" | ||||
split_paths = 1 | ||||
if repo_in_path: | ||||
split_paths = 2 | ||||
_parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths) | ||||
if repo_in_path and len(_parts) > 1: | ||||
# such case last element is the repo_name | ||||
_parts.pop(-1) | ||||
group_name_cleaned = _parts[-1] # just the group name | ||||
parent_repo_group_name = None | ||||
if len(_parts) > 1: | ||||
parent_repo_group_name = _parts[0] | ||||
r1151 | parent_group = None | |||
r1 | if parent_repo_group_name: | |||
parent_group = RepoGroup.get_by_group_name(parent_repo_group_name) | ||||
r1151 | if get_object: | |||
return group_name_cleaned, parent_repo_group_name, parent_group | ||||
r1 | return group_name_cleaned, parent_repo_group_name | |||
def check_exist_filesystem(self, group_name, exc_on_failure=True): | ||||
create_path = os.path.join(self.repos_path, group_name) | ||||
log.debug('creating new group in %s', create_path) | ||||
if os.path.isdir(create_path): | ||||
if exc_on_failure: | ||||
r1152 | abs_create_path = os.path.abspath(create_path) | |||
r5096 | raise Exception(f'Directory `{abs_create_path}` already exists !') | |||
r1 | return False | |||
return True | ||||
def _create_group(self, group_name): | ||||
""" | ||||
makes repository group on filesystem | ||||
:param repo_name: | ||||
:param parent_id: | ||||
""" | ||||
self.check_exist_filesystem(group_name) | ||||
create_path = os.path.join(self.repos_path, group_name) | ||||
log.debug('creating new group in %s', create_path) | ||||
r3268 | os.makedirs(create_path, mode=0o755) | |||
r1 | log.debug('created group in %s', create_path) | |||
def _rename_group(self, old, new): | ||||
""" | ||||
Renames a group on filesystem | ||||
:param group_name: | ||||
""" | ||||
if old == new: | ||||
log.debug('skipping group rename') | ||||
return | ||||
log.debug('renaming repository group from %s to %s', old, new) | ||||
old_path = os.path.join(self.repos_path, old) | ||||
new_path = os.path.join(self.repos_path, new) | ||||
log.debug('renaming repos paths from %s to %s', old_path, new_path) | ||||
if os.path.isdir(new_path): | ||||
raise Exception('Was trying to rename to already ' | ||||
'existing dir %s' % new_path) | ||||
shutil.move(old_path, new_path) | ||||
def _delete_filesystem_group(self, group, force_delete=False): | ||||
""" | ||||
Deletes a group from a filesystem | ||||
:param group: instance of group from database | ||||
:param force_delete: use shutil rmtree to remove all objects | ||||
""" | ||||
paths = group.full_path.split(RepoGroup.url_sep()) | ||||
paths = os.sep.join(paths) | ||||
rm_path = os.path.join(self.repos_path, paths) | ||||
log.info("Removing group %s", rm_path) | ||||
# delete only if that path really exists | ||||
if os.path.isdir(rm_path): | ||||
if force_delete: | ||||
shutil.rmtree(rm_path) | ||||
else: | ||||
# archive that group` | ||||
_now = datetime.datetime.now() | ||||
_ms = str(_now.microsecond).rjust(6, '0') | ||||
r5096 | _d = 'rm__{}_GROUP_{}'.format( | |||
r1 | _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name) | |||
shutil.move(rm_path, os.path.join(self.repos_path, _d)) | ||||
def create(self, group_name, group_description, owner, just_db=False, | ||||
r1094 | copy_permissions=False, personal=None, commit_early=True): | |||
r1 | ||||
(group_name_cleaned, | ||||
parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name) | ||||
parent_group = None | ||||
if parent_group_name: | ||||
parent_group = self._get_repo_group(parent_group_name) | ||||
r1094 | if not parent_group: | |||
# we tried to create a nested group, but the parent is not | ||||
# existing | ||||
raise ValueError( | ||||
'Parent group `%s` given in `%s` group name ' | ||||
'is not yet existing.' % (parent_group_name, group_name)) | ||||
r1 | ||||
r1094 | # because we are doing a cleanup, we need to check if such directory | |||
# already exists. If we don't do that we can accidentally delete | ||||
# existing directory via cleanup that can cause data issues, since | ||||
# delete does a folder rename to special syntax later cleanup | ||||
# functions can delete this | ||||
r1 | cleanup_group = self.check_exist_filesystem(group_name, | |||
exc_on_failure=False) | ||||
r2175 | user = self._get_user(owner) | |||
if not user: | ||||
raise ValueError('Owner %s not found as rhodecode user', owner) | ||||
r1 | try: | |||
new_repo_group = RepoGroup() | ||||
new_repo_group.user = user | ||||
new_repo_group.group_description = group_description or group_name | ||||
new_repo_group.parent_group = parent_group | ||||
new_repo_group.group_name = group_name | ||||
r1094 | new_repo_group.personal = personal | |||
r1 | ||||
self.sa.add(new_repo_group) | ||||
# create an ADMIN permission for owner except if we're super admin, | ||||
# later owner should go into the owner field of groups | ||||
if not user.is_admin: | ||||
self.grant_user_permission(repo_group=new_repo_group, | ||||
user=owner, perm='group.admin') | ||||
if parent_group and copy_permissions: | ||||
# copy permissions from parent | ||||
user_perms = UserRepoGroupToPerm.query() \ | ||||
.filter(UserRepoGroupToPerm.group == parent_group).all() | ||||
group_perms = UserGroupRepoGroupToPerm.query() \ | ||||
.filter(UserGroupRepoGroupToPerm.group == parent_group).all() | ||||
for perm in user_perms: | ||||
# don't copy over the permission for user who is creating | ||||
# this group, if he is not super admin he get's admin | ||||
# permission set above | ||||
if perm.user != user or user.is_admin: | ||||
UserRepoGroupToPerm.create( | ||||
perm.user, new_repo_group, perm.permission) | ||||
for perm in group_perms: | ||||
UserGroupRepoGroupToPerm.create( | ||||
perm.users_group, new_repo_group, perm.permission) | ||||
else: | ||||
perm_obj = self._create_default_perms(new_repo_group) | ||||
self.sa.add(perm_obj) | ||||
# now commit the changes, earlier so we are sure everything is in | ||||
# the database. | ||||
if commit_early: | ||||
self.sa.commit() | ||||
if not just_db: | ||||
self._create_group(new_repo_group.group_name) | ||||
# trigger the post hook | ||||
r4445 | from rhodecode.lib import hooks_base | |||
r1 | repo_group = RepoGroup.get_by_group_name(group_name) | |||
r3689 | ||||
# update repo group commit caches initially | ||||
repo_group.update_commit_cache() | ||||
r4445 | hooks_base.create_repository_group( | |||
r1 | created_by=user.username, **repo_group.get_dict()) | |||
Martin Bornhold
|
r557 | # Trigger create event. | ||
events.trigger(events.RepoGroupCreateEvent(repo_group)) | ||||
r1 | return new_repo_group | |||
except Exception: | ||||
self.sa.rollback() | ||||
log.exception('Exception occurred when creating repository group, ' | ||||
'doing cleanup...') | ||||
# rollback things manually ! | ||||
repo_group = RepoGroup.get_by_group_name(group_name) | ||||
if repo_group: | ||||
RepoGroup.delete(repo_group.group_id) | ||||
self.sa.commit() | ||||
if cleanup_group: | ||||
RepoGroupModel()._delete_filesystem_group(repo_group) | ||||
raise | ||||
def update_permissions( | ||||
self, repo_group, perm_additions=None, perm_updates=None, | ||||
perm_deletions=None, recursive=None, check_perms=True, | ||||
cur_user=None): | ||||
from rhodecode.model.repo import RepoModel | ||||
from rhodecode.lib.auth import HasUserGroupPermissionAny | ||||
if not perm_additions: | ||||
perm_additions = [] | ||||
if not perm_updates: | ||||
perm_updates = [] | ||||
if not perm_deletions: | ||||
perm_deletions = [] | ||||
req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') | ||||
r1799 | changes = { | |||
'added': [], | ||||
'updated': [], | ||||
r4187 | 'deleted': [], | |||
'default_user_changed': None | ||||
r1799 | } | |||
r1 | def _set_perm_user(obj, user, perm): | |||
if isinstance(obj, RepoGroup): | ||||
self.grant_user_permission( | ||||
repo_group=obj, user=user, perm=perm) | ||||
elif isinstance(obj, Repository): | ||||
# private repos will not allow to change the default | ||||
# permissions using recursive mode | ||||
if obj.private and user == User.DEFAULT_USER: | ||||
return | ||||
# we set group permission but we have to switch to repo | ||||
# permission | ||||
perm = perm.replace('group.', 'repository.') | ||||
RepoModel().grant_user_permission( | ||||
repo=obj, user=user, perm=perm) | ||||
def _set_perm_group(obj, users_group, perm): | ||||
if isinstance(obj, RepoGroup): | ||||
self.grant_user_group_permission( | ||||
repo_group=obj, group_name=users_group, perm=perm) | ||||
elif isinstance(obj, Repository): | ||||
# we set group permission but we have to switch to repo | ||||
# permission | ||||
perm = perm.replace('group.', 'repository.') | ||||
RepoModel().grant_user_group_permission( | ||||
repo=obj, group_name=users_group, perm=perm) | ||||
def _revoke_perm_user(obj, user): | ||||
if isinstance(obj, RepoGroup): | ||||
self.revoke_user_permission(repo_group=obj, user=user) | ||||
elif isinstance(obj, Repository): | ||||
RepoModel().revoke_user_permission(repo=obj, user=user) | ||||
def _revoke_perm_group(obj, user_group): | ||||
if isinstance(obj, RepoGroup): | ||||
self.revoke_user_group_permission( | ||||
repo_group=obj, group_name=user_group) | ||||
elif isinstance(obj, Repository): | ||||
RepoModel().revoke_user_group_permission( | ||||
repo=obj, group_name=user_group) | ||||
# start updates | ||||
log.debug('Now updating permissions for %s in recursive mode:%s', | ||||
repo_group, recursive) | ||||
# initialize check function, we'll call that multiple times | ||||
has_group_perm = HasUserGroupPermissionAny(*req_perms) | ||||
for obj in repo_group.recursive_groups_and_repos(): | ||||
# iterated obj is an instance of a repos group or repository in | ||||
# that group, recursive option can be: none, repos, groups, all | ||||
if recursive == 'all': | ||||
obj = obj | ||||
elif recursive == 'repos': | ||||
# skip groups, other than this one | ||||
if isinstance(obj, RepoGroup) and not obj == repo_group: | ||||
continue | ||||
elif recursive == 'groups': | ||||
# skip repos | ||||
if isinstance(obj, Repository): | ||||
continue | ||||
else: # recursive == 'none': | ||||
# DEFAULT option - don't apply to iterated objects | ||||
# also we do a break at the end of this loop. if we are not | ||||
# in recursive mode | ||||
obj = repo_group | ||||
r1799 | change_obj = obj.get_api_data() | |||
r1 | # update permissions | |||
for member_id, perm, member_type in perm_updates: | ||||
member_id = int(member_id) | ||||
if member_type == 'user': | ||||
r1799 | member_name = User.get(member_id).username | |||
r4187 | if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER: | |||
# NOTE(dan): detect if we changed permissions for default user | ||||
perm_obj = self.sa.query(UserRepoGroupToPerm) \ | ||||
.filter(UserRepoGroupToPerm.user_id == member_id) \ | ||||
.filter(UserRepoGroupToPerm.group == repo_group) \ | ||||
.scalar() | ||||
if perm_obj and perm_obj.permission.permission_name != perm: | ||||
changes['default_user_changed'] = True | ||||
r1 | # this updates also current one if found | |||
_set_perm_user(obj, user=member_id, perm=perm) | ||||
r2827 | elif member_type == 'user_group': | |||
r1 | member_name = UserGroup.get(member_id).users_group_name | |||
if not check_perms or has_group_perm(member_name, | ||||
user=cur_user): | ||||
_set_perm_group(obj, users_group=member_id, perm=perm) | ||||
r2827 | else: | |||
raise ValueError("member_type must be 'user' or 'user_group' " | ||||
"got {} instead".format(member_type)) | ||||
r1 | ||||
r1799 | changes['updated'].append( | |||
{'change_obj': change_obj, 'type': member_type, | ||||
'id': member_id, 'name': member_name, 'new_perm': perm}) | ||||
r1 | # set new permissions | |||
for member_id, perm, member_type in perm_additions: | ||||
member_id = int(member_id) | ||||
if member_type == 'user': | ||||
r1799 | member_name = User.get(member_id).username | |||
r1 | _set_perm_user(obj, user=member_id, perm=perm) | |||
r2827 | elif member_type == 'user_group': | |||
r1 | # check if we have permissions to alter this usergroup | |||
member_name = UserGroup.get(member_id).users_group_name | ||||
if not check_perms or has_group_perm(member_name, | ||||
user=cur_user): | ||||
_set_perm_group(obj, users_group=member_id, perm=perm) | ||||
r2827 | else: | |||
raise ValueError("member_type must be 'user' or 'user_group' " | ||||
"got {} instead".format(member_type)) | ||||
r1 | ||||
r1799 | changes['added'].append( | |||
{'change_obj': change_obj, 'type': member_type, | ||||
'id': member_id, 'name': member_name, 'new_perm': perm}) | ||||
r1 | # delete permissions | |||
for member_id, perm, member_type in perm_deletions: | ||||
member_id = int(member_id) | ||||
if member_type == 'user': | ||||
r1799 | member_name = User.get(member_id).username | |||
r1 | _revoke_perm_user(obj, user=member_id) | |||
r2827 | elif member_type == 'user_group': | |||
r1 | # check if we have permissions to alter this usergroup | |||
member_name = UserGroup.get(member_id).users_group_name | ||||
if not check_perms or has_group_perm(member_name, | ||||
user=cur_user): | ||||
_revoke_perm_group(obj, user_group=member_id) | ||||
r2827 | else: | |||
raise ValueError("member_type must be 'user' or 'user_group' " | ||||
"got {} instead".format(member_type)) | ||||
r1 | ||||
r1799 | changes['deleted'].append( | |||
{'change_obj': change_obj, 'type': member_type, | ||||
'id': member_id, 'name': member_name, 'new_perm': perm}) | ||||
r1 | # if it's not recursive call for all,repos,groups | |||
# break the loop and don't proceed with other changes | ||||
if recursive not in ['all', 'repos', 'groups']: | ||||
break | ||||
r1799 | return changes | |||
r1 | ||||
def update(self, repo_group, form_data): | ||||
try: | ||||
repo_group = self._get_repo_group(repo_group) | ||||
old_path = repo_group.full_path | ||||
# change properties | ||||
if 'group_description' in form_data: | ||||
repo_group.group_description = form_data['group_description'] | ||||
if 'enable_locking' in form_data: | ||||
repo_group.enable_locking = form_data['enable_locking'] | ||||
if 'group_parent_id' in form_data: | ||||
parent_group = ( | ||||
self._get_repo_group(form_data['group_parent_id'])) | ||||
repo_group.group_parent_id = ( | ||||
parent_group.group_id if parent_group else None) | ||||
repo_group.parent_group = parent_group | ||||
# mikhail: to update the full_path, we have to explicitly | ||||
# update group_name | ||||
group_name = form_data.get('group_name', repo_group.name) | ||||
repo_group.group_name = repo_group.get_new_name(group_name) | ||||
new_path = repo_group.full_path | ||||
r4662 | affected_user_ids = [] | |||
r1 | if 'user' in form_data: | |||
r4662 | old_owner_id = repo_group.user.user_id | |||
new_owner = User.get_by_username(form_data['user']) | ||||
repo_group.user = new_owner | ||||
if old_owner_id != new_owner.user_id: | ||||
affected_user_ids = [new_owner.user_id, old_owner_id] | ||||
r4000 | ||||
r1 | self.sa.add(repo_group) | |||
# iterate over all members of this groups and do fixes | ||||
# set locking if given | ||||
# if obj is a repoGroup also fix the name of the group according | ||||
# to the parent | ||||
# if obj is a Repo fix it's name | ||||
# this can be potentially heavy operation | ||||
for obj in repo_group.recursive_groups_and_repos(): | ||||
# set the value from it's parent | ||||
obj.enable_locking = repo_group.enable_locking | ||||
if isinstance(obj, RepoGroup): | ||||
new_name = obj.get_new_name(obj.name) | ||||
log.debug('Fixing group %s to new name %s', | ||||
obj.group_name, new_name) | ||||
obj.group_name = new_name | ||||
r4000 | ||||
r1 | elif isinstance(obj, Repository): | |||
# we need to get all repositories from this new group and | ||||
# rename them accordingly to new group path | ||||
new_name = obj.get_new_name(obj.just_name) | ||||
log.debug('Fixing repo %s to new name %s', | ||||
obj.repo_name, new_name) | ||||
obj.repo_name = new_name | ||||
r4000 | ||||
r1 | self.sa.add(obj) | |||
self._rename_group(old_path, new_path) | ||||
Martin Bornhold
|
r557 | # Trigger update event. | ||
events.trigger(events.RepoGroupUpdateEvent(repo_group)) | ||||
r4662 | if affected_user_ids: | |||
PermissionModel().trigger_permission_flush(affected_user_ids) | ||||
r1 | return repo_group | |||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise | ||||
def delete(self, repo_group, force_delete=False, fs_remove=True): | ||||
repo_group = self._get_repo_group(repo_group) | ||||
r731 | if not repo_group: | |||
return False | ||||
r1 | try: | |||
self.sa.delete(repo_group) | ||||
if fs_remove: | ||||
self._delete_filesystem_group(repo_group, force_delete) | ||||
else: | ||||
log.debug('skipping removal from filesystem') | ||||
Martin Bornhold
|
r557 | # Trigger delete event. | ||
events.trigger(events.RepoGroupDeleteEvent(repo_group)) | ||||
r731 | return True | |||
Martin Bornhold
|
r557 | |||
r1 | except Exception: | |||
log.error('Error removing repo_group %s', repo_group) | ||||
raise | ||||
def grant_user_permission(self, repo_group, user, perm): | ||||
""" | ||||
Grant permission for user on given repository group, or update | ||||
existing one if found | ||||
:param repo_group: Instance of RepoGroup, repositories_group_id, | ||||
or repositories_group name | ||||
:param user: Instance of User, user_id or username | ||||
:param perm: Instance of Permission, or permission_name | ||||
""" | ||||
repo_group = self._get_repo_group(repo_group) | ||||
user = self._get_user(user) | ||||
permission = self._get_perm(perm) | ||||
# check if we have that permission already | ||||
obj = self.sa.query(UserRepoGroupToPerm)\ | ||||
.filter(UserRepoGroupToPerm.user == user)\ | ||||
.filter(UserRepoGroupToPerm.group == repo_group)\ | ||||
.scalar() | ||||
if obj is None: | ||||
# create new ! | ||||
obj = UserRepoGroupToPerm() | ||||
obj.group = repo_group | ||||
obj.user = user | ||||
obj.permission = permission | ||||
self.sa.add(obj) | ||||
log.debug('Granted perm %s to %s on %s', perm, user, repo_group) | ||||
action_logger_generic( | ||||
'granted permission: {} to user: {} on repogroup: {}'.format( | ||||
perm, user, repo_group), namespace='security.repogroup') | ||||
return obj | ||||
def revoke_user_permission(self, repo_group, user): | ||||
""" | ||||
Revoke permission for user on given repository group | ||||
:param repo_group: Instance of RepoGroup, repositories_group_id, | ||||
or repositories_group name | ||||
:param user: Instance of User, user_id or username | ||||
""" | ||||
repo_group = self._get_repo_group(repo_group) | ||||
user = self._get_user(user) | ||||
obj = self.sa.query(UserRepoGroupToPerm)\ | ||||
.filter(UserRepoGroupToPerm.user == user)\ | ||||
.filter(UserRepoGroupToPerm.group == repo_group)\ | ||||
.scalar() | ||||
if obj: | ||||
self.sa.delete(obj) | ||||
log.debug('Revoked perm on %s on %s', repo_group, user) | ||||
action_logger_generic( | ||||
'revoked permission from user: {} on repogroup: {}'.format( | ||||
user, repo_group), namespace='security.repogroup') | ||||
def grant_user_group_permission(self, repo_group, group_name, perm): | ||||
""" | ||||
Grant permission for user group on given repository group, or update | ||||
existing one if found | ||||
:param repo_group: Instance of RepoGroup, repositories_group_id, | ||||
or repositories_group name | ||||
:param group_name: Instance of UserGroup, users_group_id, | ||||
or user group name | ||||
:param perm: Instance of Permission, or permission_name | ||||
""" | ||||
repo_group = self._get_repo_group(repo_group) | ||||
group_name = self._get_user_group(group_name) | ||||
permission = self._get_perm(perm) | ||||
# check if we have that permission already | ||||
obj = self.sa.query(UserGroupRepoGroupToPerm)\ | ||||
.filter(UserGroupRepoGroupToPerm.group == repo_group)\ | ||||
.filter(UserGroupRepoGroupToPerm.users_group == group_name)\ | ||||
.scalar() | ||||
if obj is None: | ||||
# create new | ||||
obj = UserGroupRepoGroupToPerm() | ||||
obj.group = repo_group | ||||
obj.users_group = group_name | ||||
obj.permission = permission | ||||
self.sa.add(obj) | ||||
log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group) | ||||
action_logger_generic( | ||||
'granted permission: {} to usergroup: {} on repogroup: {}'.format( | ||||
perm, group_name, repo_group), namespace='security.repogroup') | ||||
return obj | ||||
def revoke_user_group_permission(self, repo_group, group_name): | ||||
""" | ||||
Revoke permission for user group on given repository group | ||||
:param repo_group: Instance of RepoGroup, repositories_group_id, | ||||
or repositories_group name | ||||
:param group_name: Instance of UserGroup, users_group_id, | ||||
or user group name | ||||
""" | ||||
repo_group = self._get_repo_group(repo_group) | ||||
group_name = self._get_user_group(group_name) | ||||
obj = self.sa.query(UserGroupRepoGroupToPerm)\ | ||||
.filter(UserGroupRepoGroupToPerm.group == repo_group)\ | ||||
.filter(UserGroupRepoGroupToPerm.users_group == group_name)\ | ||||
.scalar() | ||||
if obj: | ||||
self.sa.delete(obj) | ||||
log.debug('Revoked perm to %s on %s', repo_group, group_name) | ||||
action_logger_generic( | ||||
'revoked permission from usergroup: {} on repogroup: {}'.format( | ||||
group_name, repo_group), namespace='security.repogroup') | ||||
r3689 | @classmethod | |||
def update_commit_cache(cls, repo_groups=None): | ||||
if not repo_groups: | ||||
repo_groups = RepoGroup.getAll() | ||||
for repo_group in repo_groups: | ||||
repo_group.update_commit_cache() | ||||
r1 | def get_repo_groups_as_dict(self, repo_group_list=None, admin=False, | |||
super_user_actions=False): | ||||
r1897 | from pyramid.threadlocal import get_current_request | |||
_render = get_current_request().get_partial_renderer( | ||||
r2313 | 'rhodecode:templates/data_table/_dt_elements.mako') | |||
r1897 | c = _render.get_call_context() | |||
h = _render.get_helpers() | ||||
r1 | ||||
def quick_menu(repo_group_name): | ||||
return _render('quick_repo_group_menu', repo_group_name) | ||||
def repo_group_lnk(repo_group_name): | ||||
return _render('repo_group_name', repo_group_name) | ||||
r1940 | def last_change(last_change): | |||
if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo: | ||||
r4000 | ts = time.time() | |||
utc_offset = (datetime.datetime.fromtimestamp(ts) | ||||
- datetime.datetime.utcfromtimestamp(ts)).total_seconds() | ||||
last_change = last_change + datetime.timedelta(seconds=utc_offset) | ||||
r1940 | return _render("last_change", last_change) | |||
r1102 | def desc(desc, personal): | |||
r2091 | return _render( | |||
'repo_group_desc', desc, personal, c.visual.stylify_metatags) | ||||
r1 | ||||
def repo_group_actions(repo_group_id, repo_group_name, gr_count): | ||||
return _render( | ||||
'repo_group_actions', repo_group_id, repo_group_name, gr_count) | ||||
def repo_group_name(repo_group_name, children_groups): | ||||
return _render("repo_group_name", repo_group_name, children_groups) | ||||
def user_profile(username): | ||||
return _render('user_profile', username) | ||||
repo_group_data = [] | ||||
for group in repo_group_list: | ||||
r4147 | # NOTE(marcink): because we use only raw column we need to load it like that | |||
changeset_cache = RepoGroup._load_changeset_cache( | ||||
'', group._changeset_cache) | ||||
last_commit_change = RepoGroup._load_commit_change(changeset_cache) | ||||
r1 | row = { | |||
"menu": quick_menu(group.group_name), | ||||
"name": repo_group_lnk(group.group_name), | ||||
"name_raw": group.group_name, | ||||
r4147 | ||||
"last_change": last_change(last_commit_change), | ||||
r3557 | ||||
"last_changeset": "", | ||||
"last_changeset_raw": "", | ||||
r4223 | "desc": desc(h.escape(group.group_description), group.personal), | |||
r1 | "top_level_repos": 0, | |||
r4147 | "owner": user_profile(group.User.username) | |||
r1 | } | |||
if admin: | ||||
repo_count = group.repositories.count() | ||||
r5070 | children_groups = list(map( | |||
h.safe_str, | ||||
r1 | itertools.chain((g.name for g in group.parents), | |||
r5070 | (x.name for x in [group])))) | |||
r1 | row.update({ | |||
"action": repo_group_actions( | ||||
group.group_id, group.group_name, repo_count), | ||||
"top_level_repos": repo_count, | ||||
"name": repo_group_name(group.group_name, children_groups), | ||||
}) | ||||
repo_group_data.append(row) | ||||
return repo_group_data | ||||
r2175 | ||||
r4148 | def get_repo_groups_data_table( | |||
self, draw, start, limit, | ||||
search_q, order_by, order_dir, | ||||
auth_user, repo_group_id): | ||||
from rhodecode.model.scm import RepoGroupList | ||||
_perms = ['group.read', 'group.write', 'group.admin'] | ||||
repo_groups = RepoGroup.query() \ | ||||
.filter(RepoGroup.group_parent_id == repo_group_id) \ | ||||
.all() | ||||
auth_repo_group_list = RepoGroupList( | ||||
repo_groups, perm_set=_perms, | ||||
extra_kwargs=dict(user=auth_user)) | ||||
allowed_ids = [-1] | ||||
for repo_group in auth_repo_group_list: | ||||
allowed_ids.append(repo_group.group_id) | ||||
repo_groups_data_total_count = RepoGroup.query() \ | ||||
.filter(RepoGroup.group_parent_id == repo_group_id) \ | ||||
.filter(or_( | ||||
# generate multiple IN to fix limitation problems | ||||
*in_filter_generator(RepoGroup.group_id, allowed_ids)) | ||||
) \ | ||||
.count() | ||||
base_q = Session.query( | ||||
RepoGroup.group_name, | ||||
RepoGroup.group_name_hash, | ||||
RepoGroup.group_description, | ||||
RepoGroup.group_id, | ||||
RepoGroup.personal, | ||||
RepoGroup.updated_on, | ||||
RepoGroup._changeset_cache, | ||||
User, | ||||
) \ | ||||
.filter(RepoGroup.group_parent_id == repo_group_id) \ | ||||
.filter(or_( | ||||
# generate multiple IN to fix limitation problems | ||||
*in_filter_generator(RepoGroup.group_id, allowed_ids)) | ||||
) \ | ||||
.join(User, User.user_id == RepoGroup.user_id) \ | ||||
.group_by(RepoGroup, User) | ||||
repo_groups_data_total_filtered_count = base_q.count() | ||||
sort_defined = False | ||||
if order_by == 'group_name': | ||||
sort_col = func.lower(RepoGroup.group_name) | ||||
sort_defined = True | ||||
elif order_by == 'user_username': | ||||
sort_col = User.username | ||||
else: | ||||
sort_col = getattr(RepoGroup, order_by, None) | ||||
if sort_defined or sort_col: | ||||
if order_dir == 'asc': | ||||
sort_col = sort_col.asc() | ||||
else: | ||||
sort_col = sort_col.desc() | ||||
base_q = base_q.order_by(sort_col) | ||||
base_q = base_q.offset(start).limit(limit) | ||||
repo_group_list = base_q.all() | ||||
repo_groups_data = RepoGroupModel().get_repo_groups_as_dict( | ||||
repo_group_list=repo_group_list, admin=False) | ||||
data = ({ | ||||
'draw': draw, | ||||
'data': repo_groups_data, | ||||
'recordsTotal': repo_groups_data_total_count, | ||||
'recordsFiltered': repo_groups_data_total_filtered_count, | ||||
}) | ||||
return data | ||||
r2175 | def _get_defaults(self, repo_group_name): | |||
repo_group = RepoGroup.get_by_group_name(repo_group_name) | ||||
if repo_group is None: | ||||
return None | ||||
defaults = repo_group.get_dict() | ||||
defaults['repo_group_name'] = repo_group.name | ||||
defaults['repo_group_description'] = repo_group.group_description | ||||
defaults['repo_group_enable_locking'] = repo_group.enable_locking | ||||
# we use -1 as this is how in HTML, we mark an empty group | ||||
defaults['repo_group'] = defaults['group_parent_id'] or -1 | ||||
# fill owner | ||||
if repo_group.user: | ||||
defaults.update({'user': repo_group.user.username}) | ||||
else: | ||||
replacement_user = User.get_first_super_admin().username | ||||
defaults.update({'user': replacement_user}) | ||||
return defaults | ||||