mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Validate use of existing managed repository paths
https://community.openproject.org/work_packages/74165
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user