917 lines
36 KiB
Python
917 lines
36 KiB
Python
# Copyright (C) 2011-2024 RhodeCode GmbH
|
|
#
|
|
# 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
|
|
"""
|
|
|
|
import os
|
|
import datetime
|
|
import itertools
|
|
import logging
|
|
import shutil
|
|
import time
|
|
import traceback
|
|
import string
|
|
|
|
|
|
from rhodecode import events
|
|
from rhodecode.model import BaseModel
|
|
from rhodecode.model.db import (
|
|
_hash_key,
|
|
func,
|
|
or_,
|
|
in_filter_generator,
|
|
Session,
|
|
RepoGroup,
|
|
UserRepoGroupToPerm,
|
|
User,
|
|
Permission,
|
|
UserGroupRepoGroupToPerm,
|
|
UserGroup,
|
|
Repository,
|
|
)
|
|
from rhodecode.model.permission import PermissionModel
|
|
from rhodecode.model.settings import SettingsModel
|
|
from rhodecode.lib.caching_query import FromCache
|
|
from rhodecode.lib.utils2 import action_logger_generic
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class RepoGroupModel(BaseModel):
|
|
cls = RepoGroup
|
|
PERSONAL_GROUP_DESC = "personal repo group of user `%(username)s`"
|
|
PERSONAL_GROUP_PATTERN = "${username}" # default
|
|
|
|
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)
|
|
|
|
def get_repo_group(self, repo_group):
|
|
return self._get_repo_group(repo_group)
|
|
|
|
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:
|
|
name_key = _hash_key(repo_group_name)
|
|
repo = repo.options(FromCache("sql_cache_short", f"get_repo_group_{name_key}"))
|
|
return repo.scalar()
|
|
|
|
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,
|
|
first_name=user.first_name,
|
|
last_name=user.last_name,
|
|
)
|
|
|
|
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,
|
|
)
|
|
|
|
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
|
|
repo_group_to_perm.user = def_user
|
|
return repo_group_to_perm
|
|
|
|
def _get_group_name_and_parent(self, group_name_full, repo_in_path=False, get_object=False):
|
|
"""
|
|
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]
|
|
|
|
parent_group = None
|
|
if parent_repo_group_name:
|
|
parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
|
|
|
|
if get_object:
|
|
return group_name_cleaned, parent_repo_group_name, parent_group
|
|
|
|
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("checking FS presence for repo group in %s", create_path)
|
|
|
|
if os.path.isdir(create_path):
|
|
if exc_on_failure:
|
|
abs_create_path = os.path.abspath(create_path)
|
|
raise Exception(f"Directory `{abs_create_path}` already exists !")
|
|
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)
|
|
os.makedirs(create_path, mode=0o755)
|
|
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")
|
|
_d = "rm__{}_GROUP_{}".format(_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,
|
|
copy_permissions=False,
|
|
personal=None,
|
|
commit_early=True,
|
|
):
|
|
(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)
|
|
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)
|
|
)
|
|
|
|
# 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
|
|
cleanup_group = self.check_exist_filesystem(group_name, exc_on_failure=False)
|
|
user = self._get_user(owner)
|
|
if not user:
|
|
raise ValueError("Owner %s not found as rhodecode user", owner)
|
|
|
|
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
|
|
new_repo_group.personal = personal
|
|
|
|
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
|
|
from rhodecode.lib import hooks_base
|
|
|
|
repo_group = RepoGroup.get_by_group_name(group_name)
|
|
|
|
# update repo group commit caches initially
|
|
repo_group.update_commit_cache()
|
|
|
|
hooks_base.create_repository_group(created_by=user.username, **repo_group.get_dict())
|
|
|
|
# Trigger create event.
|
|
events.trigger(events.RepoGroupCreateEvent(repo_group))
|
|
|
|
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")
|
|
|
|
changes = {"added": [], "updated": [], "deleted": [], "default_user_changed": None}
|
|
|
|
def _set_perm_user(_obj: RepoGroup | Repository, _user_obj: User, _perm):
|
|
if isinstance(_obj, RepoGroup):
|
|
self.grant_user_permission(repo_group=_obj, user=_user_obj, perm=_perm)
|
|
elif isinstance(_obj, Repository):
|
|
# private repos will not allow to change the default
|
|
# permissions using recursive mode
|
|
if _obj.private and _user_obj.username in [
|
|
User.DEFAULT_USER,
|
|
User.GHOST_USER,
|
|
User.AI_USER,
|
|
User.SECURITY_USER,
|
|
]:
|
|
log.debug("Skipping private repo %s for user %s", _obj, _user_obj)
|
|
return
|
|
|
|
# we set group permission, we have to switch to repo permission definition
|
|
new_perm = _perm.replace("group.", "repository.")
|
|
RepoModel().grant_user_permission(repo=_obj, user=_user_obj, perm=new_perm)
|
|
|
|
def _set_perm_group(_obj: RepoGroup | Repository, users_group: UserGroup, _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, we have to switch to repo permission definition
|
|
new_perm = _perm.replace("group.", "repository.")
|
|
RepoModel().grant_user_group_permission(repo=_obj, group_name=users_group, perm=new_perm)
|
|
|
|
def _revoke_perm_user(_obj: RepoGroup | Repository, _user_obj: User):
|
|
if isinstance(_obj, RepoGroup):
|
|
self.revoke_user_permission(repo_group=_obj, user=_user_obj)
|
|
elif isinstance(_obj, Repository):
|
|
# private repos will not allow to change the default
|
|
# permissions using recursive mode, also there's no revocation for default user, just update
|
|
if _user_obj.username in [User.DEFAULT_USER, User.GHOST_USER, User.AI_USER, User.SECURITY_USER]:
|
|
log.debug("Skipping private repo %s for user %s", _obj, _user_obj)
|
|
return
|
|
RepoModel().revoke_user_permission(repo=_obj, user=_user_obj)
|
|
|
|
def _revoke_perm_group(_obj: RepoGroup | Repository, user_group: UserGroup):
|
|
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
|
|
|
|
change_obj = obj.get_api_data()
|
|
|
|
# update permissions
|
|
for member_id, perm, member_type in perm_updates:
|
|
member_id = int(member_id)
|
|
if member_type == "user":
|
|
member_obj = User.get(member_id)
|
|
member_name = member_obj.username
|
|
is_repo_group = isinstance(obj, RepoGroup) and obj == repo_group
|
|
if is_repo_group and member_name in [User.GHOST_USER, User.AI_USER, User.SECURITY_USER]:
|
|
raise ValueError("Modify user permissions for %s user is not allowed." % member_name)
|
|
|
|
if is_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
|
|
|
|
# this updates also current one if found
|
|
_set_perm_user(obj, member_obj, perm)
|
|
elif member_type == "user_group":
|
|
member_obj = UserGroup.get(member_id)
|
|
member_name = member_obj.users_group_name
|
|
if not check_perms or has_group_perm(member_name, user=cur_user):
|
|
_set_perm_group(obj, member_obj, perm)
|
|
else:
|
|
raise ValueError(f"member_type must be 'user' or 'user_group' got {member_type} instead")
|
|
|
|
changes["updated"].append(
|
|
{
|
|
"change_obj": change_obj,
|
|
"type": member_type,
|
|
"id": member_id,
|
|
"name": member_name,
|
|
"new_perm": perm,
|
|
}
|
|
)
|
|
|
|
# set new permissions
|
|
for member_id, perm, member_type in perm_additions:
|
|
member_id = int(member_id)
|
|
if member_type == "user":
|
|
member_obj = User.get(member_id)
|
|
member_name = member_obj.username
|
|
_set_perm_user(obj, member_obj, perm)
|
|
elif member_type == "user_group":
|
|
# check if we have permissions to alter this usergroup
|
|
member_obj = UserGroup.get(member_id)
|
|
member_name = member_obj.users_group_name
|
|
if not check_perms or has_group_perm(member_name, user=cur_user):
|
|
_set_perm_group(obj, member_obj, perm)
|
|
else:
|
|
raise ValueError(f"member_type must be 'user' or 'user_group' got {member_type} instead")
|
|
|
|
changes["added"].append(
|
|
{
|
|
"change_obj": change_obj,
|
|
"type": member_type,
|
|
"id": member_id,
|
|
"name": member_name,
|
|
"new_perm": perm,
|
|
}
|
|
)
|
|
|
|
# delete permissions
|
|
for member_id, perm, member_type in perm_deletions:
|
|
member_id = int(member_id)
|
|
if member_type == "user":
|
|
member_obj = User.get(member_id)
|
|
member_name = member_obj.username
|
|
_revoke_perm_user(obj, member_obj)
|
|
elif member_type == "user_group":
|
|
# check if we have permissions to alter this usergroup
|
|
member_obj = UserGroup.get(member_id)
|
|
member_name = member_obj.users_group_name
|
|
if not check_perms or has_group_perm(member_name, user=cur_user):
|
|
_revoke_perm_group(obj, member_obj)
|
|
else:
|
|
raise ValueError(f"member_type must be 'user' or 'user_group' got {member_type} instead")
|
|
changes["deleted"].append(
|
|
{
|
|
"change_obj": change_obj,
|
|
"type": member_type,
|
|
"id": member_id,
|
|
"name": member_name,
|
|
"new_perm": perm,
|
|
}
|
|
)
|
|
|
|
# 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
|
|
|
|
return changes
|
|
|
|
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
|
|
|
|
affected_user_ids = []
|
|
if "user" in form_data:
|
|
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]
|
|
|
|
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
|
|
|
|
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
|
|
|
|
self.sa.add(obj)
|
|
|
|
self._rename_group(old_path, new_path)
|
|
|
|
# Trigger update event.
|
|
events.trigger(events.RepoGroupUpdateEvent(repo_group))
|
|
|
|
if affected_user_ids:
|
|
PermissionModel().trigger_permission_flush(affected_user_ids)
|
|
|
|
return repo_group
|
|
except Exception:
|
|
log.error(traceback.format_exc())
|
|
raise
|
|
|
|
def delete(self, repo_group, force_delete=False, fs_remove=True, call_events=True):
|
|
repo_group = self._get_repo_group(repo_group)
|
|
if not repo_group:
|
|
return False
|
|
repo_group_name = repo_group.group_name
|
|
try:
|
|
self.sa.delete(repo_group)
|
|
if fs_remove:
|
|
self._delete_filesystem_group(repo_group, force_delete)
|
|
else:
|
|
log.debug("skipping removal from filesystem")
|
|
|
|
# Trigger delete event.
|
|
if call_events:
|
|
events.trigger(events.RepoGroupDeleteEvent(repo_group))
|
|
|
|
except Exception:
|
|
log.error("Error removing repo_group %s", repo_group_name)
|
|
raise
|
|
|
|
return True
|
|
|
|
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",
|
|
)
|
|
|
|
@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()
|
|
|
|
def get_repo_groups_as_dict(self, repo_group_list=None, admin=False, super_user_actions=False):
|
|
from rhodecode.lib.pyramid_utils import get_current_request
|
|
|
|
_render = get_current_request().get_partial_renderer("rhodecode:templates/data_table/_dt_elements.mako")
|
|
c = _render.get_call_context()
|
|
h = _render.get_helpers()
|
|
|
|
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)
|
|
|
|
def last_change(last_change):
|
|
if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
|
|
ts = time.time()
|
|
utc_offset = (
|
|
datetime.datetime.fromtimestamp(ts) - datetime.datetime.utcfromtimestamp(ts)
|
|
).total_seconds()
|
|
last_change = last_change + datetime.timedelta(seconds=utc_offset)
|
|
return _render("last_change", last_change)
|
|
|
|
def desc(desc, personal):
|
|
return _render("repo_group_desc", desc, personal, c.visual.stylify_metatags)
|
|
|
|
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:
|
|
# 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)
|
|
row = {
|
|
"menu": quick_menu(group.group_name),
|
|
"name": repo_group_lnk(group.group_name),
|
|
"name_raw": group.group_name,
|
|
"last_change": last_change(last_commit_change),
|
|
"last_changeset": "",
|
|
"last_changeset_raw": "",
|
|
"desc": desc(h.escape(group.group_description), group.personal),
|
|
"top_level_repos": 0,
|
|
"owner": user_profile(group.User.username),
|
|
}
|
|
if admin:
|
|
repo_count = group.repositories.count()
|
|
children_groups = list(
|
|
map(h.safe_str, itertools.chain((g.name for g in group.parents), (x.name for x in [group])))
|
|
)
|
|
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
|
|
|
|
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
|
|
|
|
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
|