Validate use of existing managed repository paths

https://community.openproject.org/work_packages/74165
This commit is contained in:
Oliver Günther
2026-04-21 10:41:06 +02:00
parent cb0f96dc71
commit 24dc03c2b0
6 changed files with 190 additions and 0 deletions
+7
View File
@@ -182,10 +182,17 @@ class Repository::Git < Repository
private
def validity_of_local_url
return if managed?
parsed = URI.parse root_url.presence || url
if parsed.scheme == "ssh"
errors.add :url, :must_not_be_ssh
end
if OpenProject::SCM::LocalPathValidator.points_to_openproject_directory?(url) ||
OpenProject::SCM::LocalPathValidator.points_to_openproject_directory?(root_url)
errors.add :url, :must_not_point_to_openproject_directory
end
rescue StandardError => e
Rails.logger.error "Failed to parse repository url for validation: #{e}"
errors.add :url, :invalid_url
+10
View File
@@ -33,6 +33,7 @@ require "open_project/scm/adapters/subversion"
class Repository::Subversion < Repository
validates :url, presence: true
validates :url, format: { with: /\A(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+\z/i }
validate :validity_of_local_url
def self.scm_adapter_class
OpenProject::SCM::Adapters::Subversion
@@ -136,6 +137,15 @@ class Repository::Subversion < Repository
private
def validity_of_local_url
return if managed?
if OpenProject::SCM::LocalPathValidator.points_to_openproject_directory?(url) ||
OpenProject::SCM::LocalPathValidator.points_to_openproject_directory?(root_url)
errors.add :url, :must_not_point_to_openproject_directory
end
end
# Returns the relative url of the repository
# Eg: root_url = file:///var/svn/foo
# url = file:///var/svn/foo/bar
+1
View File
@@ -2078,6 +2078,7 @@ en:
not_whitelisted: "is not allowed by the configuration."
invalid_url: "is not a valid repository URL or path."
must_not_be_ssh: "must not be an SSH url."
must_not_point_to_openproject_directory: "must not point to an OpenProject-managed repository directory."
no_directory: "is not a directory."
role:
attributes:
@@ -0,0 +1,72 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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 General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module SCM
module LocalPathValidator
module_function
def points_to_openproject_directory?(value)
path = local_path(value)
return false if path.blank?
forbidden_roots.any? { |root| path_within_root?(path, root) }
end
def local_path(value)
return if value.blank?
parsed = URI.parse(value)
if parsed.scheme == "file"
return File.expand_path(parsed.path)
end
return File.expand_path(value) if parsed.scheme.nil? && value.start_with?("/")
rescue URI::Error
return
end
def forbidden_roots
roots = [
OpenProject::Configuration.scm_local_checkout_path,
Repository::Git.managed_root,
Repository::Subversion.managed_root
]
roots.compact_blank.map { |root| File.expand_path(root) }.uniq
end
def path_within_root?(path, root)
path == root || path.start_with?("#{root}/")
end
end
end
end
+58
View File
@@ -155,6 +155,10 @@ RSpec.describe Repository::Git do
describe "URL validation" do
let(:instance) { build(:repository_git, url:) }
let(:checkout_root) { "/tmp/git-checkouts" }
let(:managed_git_root) { "/tmp/managed-git" }
let(:managed_svn_root) { "/tmp/managed-svn" }
let(:config) { { manages: managed_git_root } }
shared_examples "repository url is valid" do
it "is valid" do
@@ -188,6 +192,60 @@ RSpec.describe Repository::Git do
expect(instance.errors.full_messages).to eq(["URL must not be an SSH url."])
end
end
context "path points to OpenProject-managed directory" do
let(:url) { "#{managed_git_root}/sample-project/.git" }
before do
allow(OpenProject::Configuration).to receive(:scm_local_checkout_path).and_return(checkout_root)
allow(Repository::Subversion).to receive(:managed_root).and_return(managed_svn_root)
end
it "is invalid" do
expect(instance).not_to be_valid
expect(instance.errors.full_messages).to include("URL must not point to an OpenProject-managed repository directory.")
end
end
context "file URL points to OpenProject-managed directory" do
let(:url) { "file://#{checkout_root}/sample-project/.git" }
before do
allow(OpenProject::Configuration).to receive(:scm_local_checkout_path).and_return(checkout_root)
allow(Repository::Subversion).to receive(:managed_root).and_return(managed_svn_root)
end
it "is invalid" do
expect(instance).not_to be_valid
expect(instance.errors.full_messages).to include("URL must not point to an OpenProject-managed repository directory.")
end
end
context "root_url points to OpenProject-managed directory" do
let(:url) { "https://example.org/repo.git" }
before do
instance.root_url = "#{managed_svn_root}/sample-project"
allow(OpenProject::Configuration).to receive(:scm_local_checkout_path).and_return(checkout_root)
allow(Repository::Subversion).to receive(:managed_root).and_return(managed_svn_root)
end
it "is invalid" do
expect(instance).not_to be_valid
expect(instance.errors.full_messages).to include("URL must not point to an OpenProject-managed repository directory.")
end
end
context "path only matches root prefix" do
let(:url) { "#{checkout_root}-other/repo.git" }
before do
allow(OpenProject::Configuration).to receive(:scm_local_checkout_path).and_return(checkout_root)
allow(Repository::Subversion).to receive(:managed_root).and_return(managed_svn_root)
end
it_behaves_like "repository url is valid"
end
end
describe "with an actual repository" do
+42
View File
@@ -147,6 +147,48 @@ RSpec.describe Repository::Subversion do
end
end
describe "URL validation" do
let(:checkout_root) { "/tmp/svn-checkouts" }
let(:managed_git_root) { "/tmp/managed-git" }
let(:managed_svn_root) { "/tmp/managed-svn" }
let(:config) { { manages: managed_svn_root } }
before do
allow(OpenProject::Configuration).to receive(:scm_local_checkout_path).and_return(checkout_root)
allow(Repository::Git).to receive(:managed_root).and_return(managed_git_root)
end
context "file URL points to OpenProject-managed directory" do
let(:instance) { build(:repository_subversion, url: "file://#{managed_git_root}/sample-project/.git") }
it "is invalid" do
expect(instance).not_to be_valid
expect(instance.errors.full_messages).to include("URL must not point to an OpenProject-managed repository directory.")
end
end
context "root_url points to OpenProject-managed directory" do
let(:instance) { build(:repository_subversion, url: "https://example.org/svn/repo") }
before do
instance.root_url = "file://#{checkout_root}/sample-project"
end
it "is invalid" do
expect(instance).not_to be_valid
expect(instance.errors.full_messages).to include("URL must not point to an OpenProject-managed repository directory.")
end
end
context "with non-managed file URL" do
let(:instance) { build(:repository_subversion, url: "file:///srv/svn/public") }
it "is valid" do
expect(instance).to be_valid
end
end
end
describe "with a remote repository" do
let(:instance) do
build(:repository_subversion,