From 13f9ccaed853654df5cfb990884345b513f0b88c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 8 Nov 2014 10:52:59 +0000 Subject: [PATCH] Adds configuration settings to limit valid repository path (#1415). git-svn-id: http://svn.redmine.org/redmine/trunk@13573 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/repositories_helper.rb | 36 ++++++++++++++++++++----- app/models/repository.rb | 13 +++++++++ app/models/repository/cvs.rb | 8 ++++++ config/configuration.yml.example | 27 +++++++++++++++++++ config/locales/en.yml | 1 + config/locales/fr.yml | 5 ++-- lib/redmine/configuration.rb | 15 +++++++++++ test/unit/repository_cvs_test.rb | 18 +++++++++++++ test/unit/repository_subversion_test.rb | 31 +++++++++++++++++++++ 9 files changed, 145 insertions(+), 9 deletions(-) diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 32b72ec54..ebe73da3f 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -152,7 +152,7 @@ module RepositoriesHelper def subversion_field_tags(form, repository) content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => !repository.safe_attribute?('url')) + - content_tag('em', '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)', :class => 'info')) + + scm_path_info_tag(repository)) + content_tag('p', form.text_field(:login, :size => 30)) + content_tag('p', form.password_field( :password, :size => 30, :name => 'ignore', @@ -165,7 +165,8 @@ module RepositoriesHelper content_tag('p', form.text_field( :url, :label => l(:field_path_to_repository), :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url'))) + + :disabled => !repository.safe_attribute?('url')) + + scm_path_info_tag(repository)) + scm_log_encoding_tag(form, repository) end @@ -175,7 +176,7 @@ module RepositoriesHelper :size => 60, :required => true, :disabled => !repository.safe_attribute?('url') ) + - content_tag('em', l(:text_mercurial_repository_note), :class => 'info')) + + scm_path_info_tag(repository)) + scm_path_encoding_tag(form, repository) end @@ -185,7 +186,7 @@ module RepositoriesHelper :size => 60, :required => true, :disabled => !repository.safe_attribute?('url') ) + - content_tag('em', l(:text_git_repository_note), :class => 'info')) + + scm_path_info_tag(repository)) + scm_path_encoding_tag(form, repository) + content_tag('p', form.check_box( :extra_report_last_commit, @@ -198,7 +199,8 @@ module RepositoriesHelper :root_url, :label => l(:field_cvsroot), :size => 60, :required => true, - :disabled => !repository.safe_attribute?('root_url'))) + + :disabled => !repository.safe_attribute?('root_url')) + + scm_path_info_tag(repository)) + content_tag('p', form.text_field( :url, :label => l(:field_cvs_module), @@ -212,7 +214,8 @@ module RepositoriesHelper content_tag('p', form.text_field( :url, :label => l(:field_path_to_repository), :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url'))) + + :disabled => !repository.safe_attribute?('url')) + + scm_path_info_tag(repository)) + scm_log_encoding_tag(form, repository) end @@ -220,10 +223,29 @@ module RepositoriesHelper content_tag('p', form.text_field( :url, :label => l(:field_root_directory), :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url'))) + + :disabled => !repository.safe_attribute?('url')) + + scm_path_info_tag(repository)) + scm_path_encoding_tag(form, repository) end + def scm_path_info_tag(repository) + text = scm_path_info(repository) + if text.present? + content_tag('em', text, :class => 'info') + else + '' + end + end + + def scm_path_info(repository) + scm_name = repository.scm_name.to_s.downcase + + info_from_config = Redmine::Configuration["scm_#{scm_name}_path_info"].presence + return info_from_config.html_safe if info_from_config + + l("text_#{scm_name}_repository_note", :default => '') + end + def scm_log_encoding_tag(form, repository) select = form.select( :log_encoding, diff --git a/app/models/repository.rb b/app/models/repository.rb index 1cab86bd1..4d81c47ab 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -45,6 +45,7 @@ class Repository < ActiveRecord::Base validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true # Checks if the SCM is enabled when creating a repository validate :repo_create_validation, :on => :create + validate :validate_repository_path attr_protected :id safe_attributes 'identifier', @@ -458,6 +459,18 @@ class Repository < ActiveRecord::Base protected + # Validates repository url based against an optional regular expression + # that can be set in the Redmine configuration file. + def validate_repository_path(attribute=:url) + regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"] + if changes[attribute] && regexp.present? + regexp = regexp.to_s.strip.gsub('%project%') {Regexp.escape(project.try(:identifier).to_s)} + unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z")) + errors.add(attribute, :invalid) + end + end + end + def check_default if !is_default? && set_as_default? self.is_default = true diff --git a/app/models/repository/cvs.rb b/app/models/repository/cvs.rb index abc408304..991c0449c 100644 --- a/app/models/repository/cvs.rb +++ b/app/models/repository/cvs.rb @@ -192,6 +192,14 @@ class Repository::Cvs < Repository @current_revision_number = nil end + protected + + # Overrides Repository#validate_repository_path to validate + # against root_url attribute. + def validate_repository_path(attribute=:root_url) + super(attribute) + end + private # Returns the next revision number to assign to a CVS changeset diff --git a/config/configuration.yml.example b/config/configuration.yml.example index da209f0bd..e6a8c6e20 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -108,6 +108,33 @@ default: scm_bazaar_command: scm_darcs_command: + # SCM paths validation. + # + # You can configure a regular expression for each SCM that will be used to + # validate the path of new repositories (eg. path entered by users with the + # "Manage repositories" permission and path returned by reposman.rb). + # The regexp will be wrapped with \A \z, so it must match the whole path. + # And the regexp is case sensitive. + # + # You can match the project identifier by using %project% in the regexp. + # + # You can also set a custom hint message for each SCM that will be displayed + # on the repository form instead of the default one. + # + # Examples: + # scm_subversion_path_regexp: file:///svnpath/[a-z0-9_]+ + # scm_subversion_path_info: SVN URL (eg. file:///svnpath/foo) + # + # scm_git_path_regexp: /gitpath/%project%(\.[a-z0-9_])?/ + # + scm_subversion_path_regexp: + scm_mercurial_path_regexp: + scm_git_path_regexp: + scm_cvs_path_regexp: + scm_bazaar_path_regexp: + scm_darcs_path_regexp: + scm_filesystem_path_regexp: + # Absolute path to the SCM commands errors (stderr) log file. # The default is to log in the 'log' directory of your Redmine instance. # Example: diff --git a/config/locales/en.yml b/config/locales/en.yml index 66de43259..f0372bccf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1052,6 +1052,7 @@ en: text_zoom_out: Zoom out text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." text_scm_path_encoding_note: "Default: UTF-8" + text_subversion_repository_note: "Examples: file:///, http://, https://, svn://, svn+[tunnelscheme]://" text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) text_scm_command: Command diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e575388e8..25693d080 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1072,8 +1072,9 @@ fr: text_zoom_out: Zoom arrière text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardé qui sera perdu si vous quittez la page." text_scm_path_encoding_note: "Défaut : UTF-8" - text_git_repository_note: "Le dépôt est vide et local (exemples : /gitrepo, c:\\gitrepo)" - text_mercurial_repository_note: "Dépôt local (exemples : /hgrepo, c:\\hgrepo)" + text_subversion_repository_note: "Exemples (en fonction des protocoles supportés) : file:///, http://, https://, svn://, svn+[tunnelscheme]://" + text_git_repository_note: "Chemin vers un dépôt vide et local (exemples : /gitrepo, c:\\gitrepo)" + text_mercurial_repository_note: "Chemin vers un dépôt local (exemples : /hgrepo, c:\\hgrepo)" text_scm_command: Commande text_scm_command_version: Version text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification. diff --git a/lib/redmine/configuration.rb b/lib/redmine/configuration.rb index 01abf4e93..77bdd8fd0 100644 --- a/lib/redmine/configuration.rb +++ b/lib/redmine/configuration.rb @@ -58,6 +58,7 @@ module Redmine end end + check_regular_expressions @config end @@ -112,6 +113,20 @@ module Redmine @config.merge!({'email_delivery' => load_from_yaml(deprecated_email_conf, env)}) end end + + # Checks the validness of regular expressions set for repository paths at startup + def check_regular_expressions + @config.each do |name, value| + if value.present? && name =~ /^scm_.+_path_regexp$/ + begin + Regexp.new value.to_s.strip + rescue => e + $stderr.puts "Invalid regular expression set as #{name} setting in your Redmine configuration file:\n#{e.message}" + exit 1 + end + end + end + end end end end diff --git a/test/unit/repository_cvs_test.rb b/test/unit/repository_cvs_test.rb index 8b5ac6271..fec41329a 100644 --- a/test/unit/repository_cvs_test.rb +++ b/test/unit/repository_cvs_test.rb @@ -93,6 +93,24 @@ class RepositoryCvsTest < ActiveSupport::TestCase assert_include str, repo.errors.full_messages end + def test_root_url_should_be_validated_against_regexp_set_in_configuration + Redmine::Configuration.with 'scm_cvs_path_regexp' => '/cvspath/[a-z]+' do + repo = Repository::Cvs.new( + :project => @project, + :identifier => 'test', + :log_encoding => 'UTF-8', + :path_encoding => '', + :url => MODULE_NAME + ) + repo.root_url = '/wrong_path' + assert !repo.valid? + assert repo.errors[:root_url].present? + + repo.root_url = '/cvspath/foo' + assert repo.valid? + end + end + if File.directory?(REPOSITORY_PATH) def test_fetch_changesets_from_scratch assert_equal 0, @repository.changesets.count diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb index 2fe50da14..42e963288 100644 --- a/test/unit/repository_subversion_test.rb +++ b/test/unit/repository_subversion_test.rb @@ -57,6 +57,37 @@ class RepositorySubversionTest < ActiveSupport::TestCase end end + def test_url_should_be_validated_against_regexp_set_in_configuration + Redmine::Configuration.with 'scm_subversion_path_regexp' => 'file:///svnpath/[a-z]+' do + repo = Repository::Subversion.new(:project => @project, :identifier => 'test') + repo.url = 'http://foo' + assert !repo.valid? + assert repo.errors[:url].present? + + repo.url = 'file:///svnpath/foo/bar' + assert !repo.valid? + assert repo.errors[:url].present? + + repo.url = 'file:///svnpath/foo' + assert repo.valid? + end + end + + def test_url_should_be_validated_against_regexp_set_in_configuration_with_project_identifier + Redmine::Configuration.with 'scm_subversion_path_regexp' => 'file:///svnpath/%project%(\.[a-z]+)?' do + repo = Repository::Subversion.new(:project => @project, :identifier => 'test') + repo.url = 'file:///svnpath/invalid' + assert !repo.valid? + assert repo.errors[:url].present? + + repo.url = 'file:///svnpath/subproject1' + assert repo.valid? + + repo.url = 'file:///svnpath/subproject1.foo' + assert repo.valid? + end + end + if repository_configured?('subversion') def test_fetch_changesets_from_scratch assert_equal 0, @repository.changesets.count -- 2.39.5