|
|
# 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 re
|
|
|
import colander
|
|
|
|
|
|
from rhodecode.model.validation_schema import preparers
|
|
|
from rhodecode.model.db import User, UserGroup
|
|
|
|
|
|
|
|
|
class _RootLocation(object):
|
|
|
pass
|
|
|
|
|
|
RootLocation = _RootLocation()
|
|
|
|
|
|
|
|
|
def _normalize(seperator, path):
|
|
|
|
|
|
if not path:
|
|
|
return ''
|
|
|
elif path is colander.null:
|
|
|
return colander.null
|
|
|
|
|
|
parts = path.split(seperator)
|
|
|
|
|
|
def bad_parts(value):
|
|
|
if not value:
|
|
|
return False
|
|
|
if re.match(r'^[.]+$', value):
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
def slugify(value):
|
|
|
value = preparers.slugify_preparer(value)
|
|
|
value = re.sub(r'[.]{2,}', '.', value)
|
|
|
return value
|
|
|
|
|
|
clean_parts = [slugify(item) for item in parts if item]
|
|
|
path = filter(bad_parts, clean_parts)
|
|
|
return seperator.join(path)
|
|
|
|
|
|
|
|
|
class RepoNameType(colander.String):
|
|
|
SEPARATOR = '/'
|
|
|
|
|
|
def deserialize(self, node, cstruct):
|
|
|
result = super().deserialize(node, cstruct)
|
|
|
if cstruct is colander.null:
|
|
|
return colander.null
|
|
|
return self._normalize(result)
|
|
|
|
|
|
def _normalize(self, path):
|
|
|
return _normalize(self.SEPARATOR, path)
|
|
|
|
|
|
|
|
|
class GroupNameType(colander.String):
|
|
|
SEPARATOR = '/'
|
|
|
|
|
|
def deserialize(self, node, cstruct):
|
|
|
if cstruct is RootLocation:
|
|
|
return cstruct
|
|
|
|
|
|
result = super().deserialize(node, cstruct)
|
|
|
if cstruct is colander.null:
|
|
|
return colander.null
|
|
|
return self._normalize(result)
|
|
|
|
|
|
def _normalize(self, path):
|
|
|
return _normalize(self.SEPARATOR, path)
|
|
|
|
|
|
|
|
|
class StringBooleanType(colander.String):
|
|
|
true_values = ['true', 't', 'yes', 'y', 'on', '1']
|
|
|
false_values = ['false', 'f', 'no', 'n', 'off', '0']
|
|
|
|
|
|
def serialize(self, node, appstruct):
|
|
|
if appstruct is colander.null:
|
|
|
return colander.null
|
|
|
if not isinstance(appstruct, bool):
|
|
|
raise colander.Invalid(node, '%r is not a boolean' % appstruct)
|
|
|
|
|
|
return appstruct and 'true' or 'false'
|
|
|
|
|
|
def deserialize(self, node, cstruct):
|
|
|
if cstruct is colander.null:
|
|
|
return colander.null
|
|
|
|
|
|
if isinstance(cstruct, bool):
|
|
|
return cstruct
|
|
|
|
|
|
if not isinstance(cstruct, str):
|
|
|
raise colander.Invalid(node, '%r is not a string' % cstruct)
|
|
|
|
|
|
value = cstruct.lower()
|
|
|
if value in self.true_values:
|
|
|
return True
|
|
|
elif value in self.false_values:
|
|
|
return False
|
|
|
else:
|
|
|
raise colander.Invalid(
|
|
|
node, f'{value} value cannot be translated to bool')
|
|
|
|
|
|
|
|
|
class UserOrUserGroupType(colander.SchemaType):
|
|
|
""" colander Schema type for valid rhodecode user and/or usergroup """
|
|
|
scopes = ('user', 'usergroup')
|
|
|
|
|
|
def __init__(self):
|
|
|
self.users = 'user' in self.scopes
|
|
|
self.usergroups = 'usergroup' in self.scopes
|
|
|
|
|
|
def serialize(self, node, appstruct):
|
|
|
if appstruct is colander.null:
|
|
|
return colander.null
|
|
|
|
|
|
if self.users:
|
|
|
if isinstance(appstruct, User):
|
|
|
if self.usergroups:
|
|
|
return 'user:%s' % appstruct.username
|
|
|
return appstruct.username
|
|
|
|
|
|
if self.usergroups:
|
|
|
if isinstance(appstruct, UserGroup):
|
|
|
if self.users:
|
|
|
return 'usergroup:%s' % appstruct.users_group_name
|
|
|
return appstruct.users_group_name
|
|
|
|
|
|
raise colander.Invalid(
|
|
|
node, '{} is not a valid {}'.format(appstruct, ' or '.join(self.scopes)))
|
|
|
|
|
|
def deserialize(self, node, cstruct):
|
|
|
if cstruct is colander.null:
|
|
|
return colander.null
|
|
|
|
|
|
user, usergroup = None, None
|
|
|
if self.users:
|
|
|
if cstruct.startswith('user:'):
|
|
|
user = User.get_by_username(cstruct.split(':')[1])
|
|
|
else:
|
|
|
user = User.get_by_username(cstruct)
|
|
|
|
|
|
if self.usergroups:
|
|
|
if cstruct.startswith('usergroup:'):
|
|
|
usergroup = UserGroup.get_by_group_name(cstruct.split(':')[1])
|
|
|
else:
|
|
|
usergroup = UserGroup.get_by_group_name(cstruct)
|
|
|
|
|
|
if self.users and self.usergroups:
|
|
|
if user and usergroup:
|
|
|
raise colander.Invalid(node, (
|
|
|
'%s is both a user and usergroup, specify which '
|
|
|
'one was wanted by prepending user: or usergroup: to the '
|
|
|
'name') % cstruct)
|
|
|
|
|
|
if self.users and user:
|
|
|
return user
|
|
|
|
|
|
if self.usergroups and usergroup:
|
|
|
return usergroup
|
|
|
|
|
|
raise colander.Invalid(
|
|
|
node, '{} is not a valid {}'.format(cstruct, ' or '.join(self.scopes)))
|
|
|
|
|
|
|
|
|
class UserType(UserOrUserGroupType):
|
|
|
scopes = ('user',)
|
|
|
|
|
|
|
|
|
class UserGroupType(UserOrUserGroupType):
|
|
|
scopes = ('usergroup',)
|
|
|
|
|
|
|
|
|
class StrOrIntType(colander.String):
|
|
|
def deserialize(self, node, cstruct):
|
|
|
if isinstance(cstruct, str):
|
|
|
return super().deserialize(node, cstruct)
|
|
|
else:
|
|
|
return colander.Integer().deserialize(node, cstruct)
|
|
|
|