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 ...@@ -825,6 +825,21 @@ class Project < ApplicationRecord
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id) from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end 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 end
def initialize(attributes = nil) 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 ...@@ -218,6 +218,7 @@ module EE
less_than: ::Gitlab::Pages::MAX_SIZE / 1.megabyte } less_than: ::Gitlab::Pages::MAX_SIZE / 1.megabyte }
validates :approvals_before_merge, numericality: true, allow_blank: true validates :approvals_before_merge, numericality: true, allow_blank: true
validate :import_url_inside_fork_network, if: :import_url_changed?
with_options if: :mirror? do with_options if: :mirror? do
validates :import_url, presence: true validates :import_url, presence: true
...@@ -852,5 +853,19 @@ module EE ...@@ -852,5 +853,19 @@ module EE
pipeline_scope.with_reports(::Ci::JobArtifact.security_reports).first || pipeline_scope.with_reports(::Ci::JobArtifact.security_reports).first ||
pipeline_scope.with_legacy_security_reports.first pipeline_scope.with_legacy_security_reports.first
end 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
end end
...@@ -2412,6 +2412,61 @@ RSpec.describe Project do ...@@ -2412,6 +2412,61 @@ RSpec.describe Project do
expect(project.reload.import_url).to eq('http://user:pass@test.com') expect(project.reload.import_url).to eq('http://user:pass@test.com')
end end
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 end
describe '#add_import_job' do describe '#add_import_job' do
......
...@@ -39189,6 +39189,9 @@ msgstr "" ...@@ -39189,6 +39189,9 @@ msgstr ""
msgid "must be greater than start date" msgid "must be greater than start date"
msgstr "" msgstr ""
msgid "must be inside the fork network"
msgstr ""
msgid "must have a unique schedule, status, and elapsed time" msgid "must have a unique schedule, status, and elapsed time"
msgstr "" msgstr ""
......
...@@ -1661,6 +1661,45 @@ RSpec.describe Project, factory_default: :keep do ...@@ -1661,6 +1661,45 @@ RSpec.describe Project, factory_default: :keep do
end end
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 context 'repository storage by default' do
let(:project) { build(:project) } 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