Commit d08dbd1f authored by Igor Drozdov's avatar Igor Drozdov

Merge branch 'block-mirrors-outside-fork-network' into 'master'

Implement fork network mirroring rule [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!60735
parents bf78aec2 b21db42e
......@@ -825,6 +825,21 @@ class Project < ApplicationRecord
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end
def find_by_url(url)
uri = URI(url)
return unless uri.host == Gitlab.config.gitlab.host
match = Rails.application.routes.recognize_path(url)
return if match[:unmatched_route].present?
return if match[:namespace_id].blank? || match[:id].blank?
find_by_full_path(match.values_at(:namespace_id, :id).join("/"))
rescue ActionController::RoutingError, URI::InvalidURIError
nil
end
end
def initialize(attributes = nil)
......
---
name: block_external_fork_network_mirrors
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60735
rollout_issue_url:
milestone: '14.0'
type: development
group: group::source code
default_enabled: false
......@@ -218,6 +218,7 @@ module EE
less_than: ::Gitlab::Pages::MAX_SIZE / 1.megabyte }
validates :approvals_before_merge, numericality: true, allow_blank: true
validate :import_url_inside_fork_network, if: :import_url_changed?
with_options if: :mirror? do
validates :import_url, presence: true
......@@ -852,5 +853,19 @@ module EE
pipeline_scope.with_reports(::Ci::JobArtifact.security_reports).first ||
pipeline_scope.with_legacy_security_reports.first
end
# If the project is inside a fork network, the mirror URL must
# also belong to a member of that fork network
def import_url_inside_fork_network
return unless ::Feature.enabled?(:block_external_fork_network_mirrors, self, default_enabled: :yaml)
if forked?
mirror_project = ::Project.find_by_url(import_url)
unless mirror_project.present? && fork_network_projects.include?(mirror_project)
errors.add(:url, _("must be inside the fork network"))
end
end
end
end
end
......@@ -2412,6 +2412,61 @@ RSpec.describe Project do
expect(project.reload.import_url).to eq('http://user:pass@test.com')
end
end
context 'project is inside a fork network' do
subject { project }
let(:project) { create(:project, fork_network: fork_network) }
let(:fork_network) { create(:fork_network) }
before do
stub_config_setting(host: 'gitlab.com')
end
context 'feature flag is disabled' do
before do
stub_feature_flags(block_external_fork_network_mirrors: false)
project.import_url = "https://customgitlab.com/foo/bar.git"
end
it { is_expected.to be_valid }
end
context 'the project is the root of the fork network' do
before do
project.import_url = "https://customgitlab.com/foo/bar.git"
expect(fork_network).to receive(:root_project).and_return(project)
end
it { is_expected.to be_valid }
end
context 'the URL is inside the fork network' do
before do
project.import_url = "https://#{Gitlab.config.gitlab.host}/#{project.fork_network.root_project.full_path}.git"
end
it { is_expected.to be_valid }
end
context 'the URL is external but the project exists' do
it 'raises an error' do
project.import_url = "https://customgitlab.com/#{project.fork_network.root_project.full_path}.git"
project.validate
expect(project.errors[:url]).to include('must be inside the fork network')
end
end
context 'the URL is not inside the fork network' do
it 'raises an error' do
project.import_url = "https://customgitlab.com/foo/bar.git"
project.validate
expect(project.errors[:url]).to include('must be inside the fork network')
end
end
end
end
describe '#add_import_job' do
......
......@@ -39189,6 +39189,9 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
msgid "must be inside the fork network"
msgstr ""
msgid "must have a unique schedule, status, and elapsed time"
msgstr ""
......
......@@ -1661,6 +1661,45 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '.find_by_url' do
subject { described_class.find_by_url(url) }
let_it_be(:project) { create(:project) }
before do
stub_config_setting(host: 'gitlab.com')
end
context 'url is internal' do
let(:url) { "https://#{Gitlab.config.gitlab.host}/#{path}" }
context 'path is recognised as a project path' do
let(:path) { project.full_path }
it { is_expected.to eq(project) }
it 'returns nil if the path detection throws an error' do
expect(Rails.application.routes).to receive(:recognize_path).with(url) { raise ActionController::RoutingError, 'test' }
expect { subject }.not_to raise_error(ActionController::RoutingError)
expect(subject).to be_nil
end
end
context 'path is not a project path' do
let(:path) { 'probably/missing.git' }
it { is_expected.to be_nil }
end
end
context 'url is external' do
let(:url) { "https://foo.com/bar/baz.git" }
it { is_expected.to be_nil }
end
end
context 'repository storage by default' do
let(:project) { build(:project) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment