diff --git a/rhodecode/apps/repository/views/repo_settings.py b/rhodecode/apps/repository/views/repo_settings.py --- a/rhodecode/apps/repository/views/repo_settings.py +++ b/rhodecode/apps/repository/views/repo_settings.py @@ -80,6 +80,7 @@ class RepoSettingsView(RepoAppView): def _get_schema(self, c, old_values=None): return repo_schema.RepoSettingsSchema().bind( + repo_type=self.db_repo.repo_type, repo_type_options=[self.db_repo.repo_type], repo_ref_options=c.landing_revs_choices, repo_ref_items=c.landing_revs, diff --git a/rhodecode/model/validation_schema/schemas/repo_schema.py b/rhodecode/model/validation_schema/schemas/repo_schema.py --- a/rhodecode/model/validation_schema/schemas/repo_schema.py +++ b/rhodecode/model/validation_schema/schemas/repo_schema.py @@ -66,6 +66,13 @@ def deferred_landing_ref_validator(node, @colander.deferred +def deferred_clone_uri_validator(node, kw): + repo_type = kw.get('repo_type') + validator = validators.CloneUriValidator(repo_type) + return validator + + +@colander.deferred def deferred_landing_ref_widget(node, kw): items = kw.get( 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)]) @@ -358,3 +365,50 @@ class RepoSchema(colander.MappingSchema) third.deserialize({'unique_repo_name': validated_name}) return appstruct + + +class RepoSettingsSchema(RepoSchema): + repo_group = colander.SchemaNode( + colander.Integer(), + validator=deferred_repo_group_validator, + widget=deferred_repo_group_widget, + missing='') + + repo_clone_uri_change = colander.SchemaNode( + colander.String(), + missing='NEW') + + repo_clone_uri = colander.SchemaNode( + colander.String(), + preparers=[preparers.strip_preparer], + validator=deferred_clone_uri_validator, + 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(RepoSchema, self).deserialize(cstruct) + validated_name = appstruct['repo_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 + 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 = RepoNameUniqueSchema().bind(**self.bindings) + third.deserialize({'unique_repo_name': validated_name}) + + return appstruct diff --git a/rhodecode/model/validation_schema/validators.py b/rhodecode/model/validation_schema/validators.py --- a/rhodecode/model/validation_schema/validators.py +++ b/rhodecode/model/validation_schema/validators.py @@ -1,5 +1,27 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2011-2017 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 . +# +# 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 re +import logging + import ipaddress import colander @@ -7,6 +29,8 @@ import colander from rhodecode.translation import _ from rhodecode.lib.utils2 import glob2re +log = logging.getLogger(__name__) + def ip_addr_validator(node, value): try: @@ -46,3 +70,71 @@ def valid_name_validator(node, value): msg = _('Name must start with a letter or number. Got `{}`').format(value) if not re.match(r'^[a-zA-z0-9]{1,}', value): raise colander.Invalid(node, msg) + + +class InvalidCloneUrl(Exception): + allowed_prefixes = () + + +def url_validator(url, repo_type, config): + from rhodecode.lib.vcs.backends.hg import MercurialRepository + from rhodecode.lib.vcs.backends.git import GitRepository + from rhodecode.lib.vcs.backends.svn import SubversionRepository + + if repo_type == 'hg': + allowed_prefixes = ('http', 'svn+http', 'git+http') + + if 'http' in url[:4]: + # initially check if it's at least the proper URL + # or does it pass basic auth + + MercurialRepository.check_url(url, config) + elif 'svn+http' in url[:8]: # svn->hg import + SubversionRepository.check_url(url, config) + elif 'git+http' in url[:8]: # git->hg import + raise NotImplementedError() + else: + exc = InvalidCloneUrl('Clone from URI %s not allowed. ' + 'Allowed url must start with one of %s' + % (url, ','.join(allowed_prefixes))) + exc.allowed_prefixes = allowed_prefixes + raise exc + + elif repo_type == 'git': + allowed_prefixes = ('http', 'svn+http', 'hg+http') + if 'http' in url[:4]: + # initially check if it's at least the proper URL + # or does it pass basic auth + GitRepository.check_url(url, config) + elif 'svn+http' in url[:8]: # svn->git import + raise NotImplementedError() + elif 'hg+http' in url[:8]: # hg->git import + raise NotImplementedError() + else: + exc = InvalidCloneUrl('Clone from URI %s not allowed. ' + 'Allowed url must start with one of %s' + % (url, ','.join(allowed_prefixes))) + exc.allowed_prefixes = allowed_prefixes + raise exc + + +class CloneUriValidator(object): + def __init__(self, repo_type): + self.repo_type = repo_type + + def __call__(self, node, value): + from rhodecode.lib.utils import make_db_config + try: + config = make_db_config(clear_session=False) + url_validator(value, self.repo_type, config) + except InvalidCloneUrl as e: + log.warning(e) + msg = _(u'Invalid clone url, provide a valid clone ' + u'url starting with one of {allowed_prefixes}').format( + allowed_prefixes=e.allowed_prefixes) + raise colander.Invalid(node, msg) + except Exception: + log.exception('Url validation failed') + msg = _(u'invalid clone url for {repo_type} repository').format( + repo_type=self.repo_type) + raise colander.Invalid(node, msg) diff --git a/rhodecode/templates/admin/repos/repo_edit_settings.mako b/rhodecode/templates/admin/repos/repo_edit_settings.mako --- a/rhodecode/templates/admin/repos/repo_edit_settings.mako +++ b/rhodecode/templates/admin/repos/repo_edit_settings.mako @@ -52,18 +52,29 @@
%if c.rhodecode_db_repo.clone_uri: - ## display + ## display, if we don't have any errors + % if not c.form['repo_clone_uri'].error:
${c.rhodecode_db_repo.clone_uri_hidden} ${_('edit')}
+ % endif + ## alter field -