##// END OF EJS Templates
deps: bumped pycryptodome==3.21.0 for security issue
deps: bumped pycryptodome==3.21.0 for security issue

File last commit:

r5608:6d33e504 default
r5640:acc4336c default
Show More
repo_group_schema.py
311 lines | 11.3 KiB | text/x-python | PythonLexer
# Copyright (C) 2016-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/
import colander
import deform.widget
from rhodecode.model.validation_schema.utils import username_converter
from rhodecode.translation import _
from rhodecode.model.validation_schema import validators, preparers, types
def get_group_and_repo(repo_name):
from rhodecode.model.repo_group import RepoGroupModel
return RepoGroupModel()._get_group_name_and_parent(
repo_name, get_object=True)
def get_repo_group(repo_group_id):
from rhodecode.model.repo_group import RepoGroup
return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
@colander.deferred
def deferred_can_write_to_group_validator(node, kw):
old_values = kw.get('old_values') or {}
request_user = kw.get('user')
def can_write_group_validator(node, value):
from rhodecode.lib.auth import (
HasPermissionAny, HasRepoGroupPermissionAny)
from rhodecode.model.repo_group import RepoGroupModel
messages = {
'invalid_parent_repo_group':
_("Parent repository group `{}` does not exist"),
# permissions denied we expose as not existing, to prevent
# resource discovery
'permission_denied_parent_group':
_("You do not have the permissions to store "
"repository groups inside repository group `{}`"),
'permission_denied_root':
_("You do not have the permission to store "
"repository groups in the root location.")
}
value = value['repo_group_name']
parent_group_name = value
is_root_location = value is types.RootLocation
# NOT initialized validators, we must call them
can_create_repo_groups_at_root = HasPermissionAny(
'hg.admin', 'hg.repogroup.create.true')
if is_root_location:
if can_create_repo_groups_at_root(user=request_user):
# we can create repo group inside tool-level. No more checks
# are required
return
else:
raise colander.Invalid(node, messages['permission_denied_root'])
# check if the parent repo group actually exists
parent_group = None
if parent_group_name:
parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
if value and not parent_group:
raise colander.Invalid(
node, messages['invalid_parent_repo_group'].format(
parent_group_name))
# check if we have permissions to create new groups under
# parent repo group
# create repositories with write permission on group is set to true
create_on_write = HasPermissionAny(
'hg.create.write_on_repogroup.true')(user=request_user)
group_admin = HasRepoGroupPermissionAny('group.admin')(
parent_group_name, 'can write into group validator', user=request_user)
group_write = HasRepoGroupPermissionAny('group.write')(
parent_group_name, 'can write into group validator', user=request_user)
# creation by write access is currently disabled. Needs thinking if
# we want to allow this...
forbidden = not (group_admin or (group_write and create_on_write and 0))
old_name = old_values.get('group_name')
if old_name and old_name == old_values.get('submitted_repo_group_name'):
# we're editing a repository group, we didn't change the name
# we skip the check for write into parent group now
# this allows changing settings for this repo group
return
if parent_group and forbidden:
msg = messages['permission_denied_parent_group'].format(parent_group_name)
raise colander.Invalid(node, msg)
return can_write_group_validator
@colander.deferred
def deferred_repo_group_owner_validator(node, kw):
def repo_owner_validator(node, value):
from rhodecode.model.db import User
value = username_converter(value)
existing = User.get_by_username(value)
if not existing:
msg = _('Repo group owner with id `{}` does not exists').format(
value)
raise colander.Invalid(node, msg)
return repo_owner_validator
@colander.deferred
def deferred_unique_name_validator(node, kw):
request_user = kw.get('user')
old_values = kw.get('old_values') or {}
def unique_name_validator(node, value):
from rhodecode.model.db import Repository, RepoGroup
name_changed = value != old_values.get('group_name')
existing = Repository.get_by_repo_name(value)
if name_changed and existing:
msg = _('Repository with name `{}` already exists').format(value)
raise colander.Invalid(node, msg)
existing_group = RepoGroup.get_by_group_name(value)
if name_changed and existing_group:
msg = _('Repository group with name `{}` already exists').format(
value)
raise colander.Invalid(node, msg)
return unique_name_validator
@colander.deferred
def deferred_repo_group_name_validator(node, kw):
return validators.valid_name_validator
@colander.deferred
def deferred_repo_group_validator(node, kw):
options = kw.get(
'repo_group_repo_group_options')
return colander.OneOf([x for x in options])
@colander.deferred
def deferred_repo_group_widget(node, kw):
items = kw.get('repo_group_repo_group_items')
return deform.widget.Select2Widget(values=items)
class GroupType(colander.Mapping):
def _validate(self, node, value):
try:
return dict(repo_group_name=value)
except Exception as e:
raise colander.Invalid(
node, '"${val}" is not a mapping type: ${err}'.format(
val=value, err=e))
def deserialize(self, node, cstruct):
if cstruct is colander.null:
return cstruct
appstruct = super().deserialize(node, cstruct)
validated_name = appstruct['repo_group_name']
# inject group based on once deserialized data
(repo_group_name_without_group,
parent_group_name,
parent_group) = get_group_and_repo(validated_name)
appstruct['repo_group_name_with_group'] = validated_name
appstruct['repo_group_name_without_group'] = repo_group_name_without_group
appstruct['repo_group_name'] = parent_group_name or types.RootLocation
if parent_group:
appstruct['repo_group_id'] = parent_group.group_id
return appstruct
class GroupSchema(colander.SchemaNode):
schema_type = GroupType
validator = deferred_can_write_to_group_validator
missing = colander.null
class RepoGroup(GroupSchema):
repo_group_name = colander.SchemaNode(
types.GroupNameType())
repo_group_id = colander.SchemaNode(
colander.String(), missing=None)
repo_group_name_without_group = colander.SchemaNode(
colander.String(), missing=None)
class RepoGroupAccessSchema(colander.MappingSchema):
repo_group = RepoGroup()
class RepoGroupNameUniqueSchema(colander.MappingSchema):
unique_repo_group_name = colander.SchemaNode(
colander.String(),
validator=deferred_unique_name_validator)
class RepoGroupSchema(colander.Schema):
repo_group_name = colander.SchemaNode(
types.GroupNameType(),
validator=deferred_repo_group_name_validator)
repo_group_owner = colander.SchemaNode(
colander.String(),
validator=deferred_repo_group_owner_validator)
repo_group_description = colander.SchemaNode(
colander.String(), missing='', widget=deform.widget.TextAreaWidget())
repo_group_copy_permissions = colander.SchemaNode(
types.StringBooleanType(),
missing=False, widget=deform.widget.CheckboxWidget())
repo_group_enable_locking = colander.SchemaNode(
types.StringBooleanType(),
missing=False, widget=deform.widget.CheckboxWidget())
def deserialize(self, cstruct):
"""
Custom deserialize that allows to chain validation, and verify
permissions, and as last step uniqueness
"""
appstruct = super().deserialize(cstruct)
validated_name = appstruct['repo_group_name']
# second pass to validate permissions to repo_group
if 'old_values' in self.bindings:
# save current repo name for name change checks
self.bindings['old_values']['submitted_repo_group_name'] = validated_name
second = RepoGroupAccessSchema().bind(**self.bindings)
appstruct_second = second.deserialize({'repo_group': validated_name})
# save result
appstruct['repo_group'] = appstruct_second['repo_group']
# thirds to validate uniqueness
third = RepoGroupNameUniqueSchema().bind(**self.bindings)
third.deserialize({'unique_repo_group_name': validated_name})
return appstruct
class RepoGroupSettingsSchema(RepoGroupSchema):
repo_group = colander.SchemaNode(
colander.Integer(),
validator=deferred_repo_group_validator,
widget=deferred_repo_group_widget,
missing='')
def deserialize(self, cstruct):
"""
Custom deserialize that allows to chain validation, and verify
permissions, and as last step uniqueness
"""
# first pass, to validate given data
appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
validated_name = appstruct['repo_group_name']
# because of repoSchema adds repo-group as an ID, we inject it as
# full name here because validators require it, it's unwrapped later
# so it's safe to use and final name is going to be without group anyway
group, separator = get_repo_group(appstruct['repo_group'])
if group:
validated_name = separator.join([group.group_name, validated_name])
# second pass to validate permissions to repo_group
if 'old_values' in self.bindings:
# save current repo name for name change checks
self.bindings['old_values']['submitted_repo_group_name'] = validated_name
second = RepoGroupAccessSchema().bind(**self.bindings)
appstruct_second = second.deserialize({'repo_group': validated_name})
# save result
appstruct['repo_group'] = appstruct_second['repo_group']
# thirds to validate uniqueness
third = RepoGroupNameUniqueSchema().bind(**self.bindings)
third.deserialize({'unique_repo_group_name': validated_name})
return appstruct