|
|
# 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 os
|
|
|
|
|
|
import deform
|
|
|
import colander
|
|
|
|
|
|
from rhodecode.translation import _
|
|
|
from rhodecode.model.db import Repository, RepoGroup
|
|
|
from rhodecode.model.validation_schema import validators, preparers
|
|
|
|
|
|
|
|
|
def integration_scope_choices(permissions):
|
|
|
"""
|
|
|
Return list of (value, label) choices for integration scopes depending on
|
|
|
the permissions
|
|
|
"""
|
|
|
result = [('', _('Pick a scope:'))]
|
|
|
if 'hg.admin' in permissions['global']:
|
|
|
result.extend([
|
|
|
('global', _('Global (all repositories)')),
|
|
|
('root-repos', _('Top level repositories only')),
|
|
|
])
|
|
|
|
|
|
repo_choices = [
|
|
|
('repo:%s' % repo_name, '/' + repo_name)
|
|
|
for repo_name, repo_perm
|
|
|
in list(permissions['repositories'].items())
|
|
|
if repo_perm == 'repository.admin'
|
|
|
]
|
|
|
repogroup_choices = [
|
|
|
('repogroup:%s' % repo_group_name, '/' + repo_group_name + '/ (child repos only)')
|
|
|
for repo_group_name, repo_group_perm
|
|
|
in list(permissions['repositories_groups'].items())
|
|
|
if repo_group_perm == 'group.admin'
|
|
|
]
|
|
|
repogroup_recursive_choices = [
|
|
|
('repogroup-recursive:%s' % repo_group_name, '/' + repo_group_name + '/ (recursive)')
|
|
|
for repo_group_name, repo_group_perm
|
|
|
in list(permissions['repositories_groups'].items())
|
|
|
if repo_group_perm == 'group.admin'
|
|
|
]
|
|
|
result.extend(
|
|
|
sorted(repogroup_recursive_choices + repogroup_choices + repo_choices,
|
|
|
key=lambda choice_label: choice_label[0].split(':', 1)[1]
|
|
|
)
|
|
|
)
|
|
|
return result
|
|
|
|
|
|
|
|
|
@colander.deferred
|
|
|
def deferred_integration_scopes_validator(node, kw):
|
|
|
perms = kw.get('permissions')
|
|
|
def _scope_validator(_node, scope):
|
|
|
is_super_admin = 'hg.admin' in perms['global']
|
|
|
|
|
|
if scope.get('repo'):
|
|
|
if (is_super_admin or perms['repositories'].get(
|
|
|
scope['repo'].repo_name) == 'repository.admin'):
|
|
|
return True
|
|
|
msg = _('Only repo admins can create integrations')
|
|
|
raise colander.Invalid(_node, msg)
|
|
|
elif scope.get('repo_group'):
|
|
|
if (is_super_admin or perms['repositories_groups'].get(
|
|
|
scope['repo_group'].group_name) == 'group.admin'):
|
|
|
return True
|
|
|
|
|
|
msg = _('Only repogroup admins can create integrations')
|
|
|
raise colander.Invalid(_node, msg)
|
|
|
else:
|
|
|
if is_super_admin:
|
|
|
return True
|
|
|
msg = _('Only superadmins can create global integrations')
|
|
|
raise colander.Invalid(_node, msg)
|
|
|
|
|
|
return _scope_validator
|
|
|
|
|
|
|
|
|
@colander.deferred
|
|
|
def deferred_integration_scopes_widget(node, kw):
|
|
|
if kw.get('no_scope'):
|
|
|
return deform.widget.TextInputWidget(readonly=True)
|
|
|
|
|
|
choices = integration_scope_choices(kw.get('permissions'))
|
|
|
widget = deform.widget.Select2Widget(values=choices)
|
|
|
return widget
|
|
|
|
|
|
|
|
|
class IntegrationScopeType(colander.SchemaType):
|
|
|
def serialize(self, node, appstruct):
|
|
|
if appstruct is colander.null:
|
|
|
return colander.null
|
|
|
|
|
|
if appstruct.get('repo'):
|
|
|
return 'repo:%s' % appstruct['repo'].repo_name
|
|
|
elif appstruct.get('repo_group'):
|
|
|
if appstruct.get('child_repos_only'):
|
|
|
return 'repogroup:%s' % appstruct['repo_group'].group_name
|
|
|
else:
|
|
|
return 'repogroup-recursive:%s' % (
|
|
|
appstruct['repo_group'].group_name)
|
|
|
else:
|
|
|
if appstruct.get('child_repos_only'):
|
|
|
return 'root-repos'
|
|
|
else:
|
|
|
return 'global'
|
|
|
|
|
|
def deserialize(self, node, cstruct):
|
|
|
if cstruct is colander.null:
|
|
|
return colander.null
|
|
|
|
|
|
if cstruct.startswith('repo:'):
|
|
|
repo = Repository.get_by_repo_name(cstruct.split(':')[1])
|
|
|
if repo:
|
|
|
return {
|
|
|
'repo': repo,
|
|
|
'repo_group': None,
|
|
|
'child_repos_only': False,
|
|
|
}
|
|
|
elif cstruct.startswith('repogroup-recursive:'):
|
|
|
repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
|
|
|
if repo_group:
|
|
|
return {
|
|
|
'repo': None,
|
|
|
'repo_group': repo_group,
|
|
|
'child_repos_only': False
|
|
|
}
|
|
|
elif cstruct.startswith('repogroup:'):
|
|
|
repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
|
|
|
if repo_group:
|
|
|
return {
|
|
|
'repo': None,
|
|
|
'repo_group': repo_group,
|
|
|
'child_repos_only': True
|
|
|
}
|
|
|
elif cstruct == 'global':
|
|
|
return {
|
|
|
'repo': None,
|
|
|
'repo_group': None,
|
|
|
'child_repos_only': False
|
|
|
}
|
|
|
elif cstruct == 'root-repos':
|
|
|
return {
|
|
|
'repo': None,
|
|
|
'repo_group': None,
|
|
|
'child_repos_only': True
|
|
|
}
|
|
|
|
|
|
raise colander.Invalid(node, '%r is not a valid scope' % cstruct)
|
|
|
|
|
|
|
|
|
class IntegrationOptionsSchemaBase(colander.MappingSchema):
|
|
|
|
|
|
name = colander.SchemaNode(
|
|
|
colander.String(),
|
|
|
description=_('Short name for this integration.'),
|
|
|
missing=colander.required,
|
|
|
title=_('Integration name'),
|
|
|
)
|
|
|
|
|
|
scope = colander.SchemaNode(
|
|
|
IntegrationScopeType(),
|
|
|
description=_(
|
|
|
'Scope of the integration. Recursive means the integration '
|
|
|
' runs on all repos of that group and children recursively.'),
|
|
|
title=_('Integration scope'),
|
|
|
validator=deferred_integration_scopes_validator,
|
|
|
widget=deferred_integration_scopes_widget,
|
|
|
missing=colander.required,
|
|
|
)
|
|
|
|
|
|
enabled = colander.SchemaNode(
|
|
|
colander.Bool(),
|
|
|
default=True,
|
|
|
description=_('Enable or disable this integration.'),
|
|
|
missing=False,
|
|
|
title=_('Enabled'),
|
|
|
)
|
|
|
|
|
|
|
|
|
def make_integration_schema(IntegrationType, settings=None):
|
|
|
"""
|
|
|
Return a colander schema for an integration type
|
|
|
|
|
|
:param IntegrationType: the integration type class
|
|
|
:param settings: existing integration settings dict (optional)
|
|
|
"""
|
|
|
|
|
|
settings = settings or {}
|
|
|
settings_schema = IntegrationType(settings=settings).settings_schema()
|
|
|
|
|
|
class IntegrationSchema(colander.Schema):
|
|
|
options = IntegrationOptionsSchemaBase()
|
|
|
|
|
|
schema = IntegrationSchema()
|
|
|
schema['options'].title = _('General integration options')
|
|
|
|
|
|
settings_schema.name = 'settings'
|
|
|
settings_schema.title = _('{integration_type} settings').format(
|
|
|
integration_type=IntegrationType.display_name)
|
|
|
schema.add(settings_schema)
|
|
|
|
|
|
return schema
|
|
|
|
|
|
|
|
|
|