From 2f2ea07664e8841e2f4a797312371220244d3f46 Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Wed, 2 Nov 2022 16:15:16 -0400 Subject: [PATCH 01/12] Make existing scope use outbound As we introduce the inbound scope direction we need to ensure that the existing queries look at the outbound scope for determining which projects are in scope in the existing job token scope feature Changelog: changed --- app/models/ci/job_token/inbound_scope.rb | 53 ++++++++ app/models/ci/job_token/outbound_scope.rb | 51 ++++++++ app/models/ci/job_token/project_scope_link.rb | 4 + app/models/ci/job_token/scope.rb | 44 ++++--- .../models/ci/job_token/inbound_scope_spec.rb | 119 ++++++++++++++++++ .../ci/job_token/outbound_scope_spec.rb | 101 +++++++++++++++ spec/models/ci/job_token/scope_spec.rb | 84 +++++++++---- 7 files changed, 409 insertions(+), 47 deletions(-) create mode 100644 app/models/ci/job_token/inbound_scope.rb create mode 100644 app/models/ci/job_token/outbound_scope.rb create mode 100644 spec/models/ci/job_token/inbound_scope_spec.rb create mode 100644 spec/models/ci/job_token/outbound_scope_spec.rb diff --git a/app/models/ci/job_token/inbound_scope.rb b/app/models/ci/job_token/inbound_scope.rb new file mode 100644 index 00000000000000..9808b3261eb1ab --- /dev/null +++ b/app/models/ci/job_token/inbound_scope.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Ci + module JobToken + class InboundScope + attr_reader :source_project + + def initialize(source_project) + @source_project = source_project + end + + def includes?(target_project) + # if the flag is disabled any project is considered to be in scope. + return true unless Feature.enabled?(:ci_inbound_job_token_scope, source_project) + # if the setting is disabled any project is considered to be in scope. + return true unless source_project.ci_inbound_job_token_scope_enabled? + + self_referential?(target_project) || added?(target_project) + end + + def all_projects + Project.from_union(target_projects, remove_duplicates: false) + end + + private + + def self_referential?(target_project) + target_project.id == source_project.id + end + + def added?(target_project) + Ci::JobToken::ProjectScopeLink + .added_project(source_project, target_project) + .inbound + .exists? + end + + def target_project_ids + Ci::JobToken::ProjectScopeLink + .from_project(source_project) + .inbound + .pluck(:target_project_id) + end + + def target_projects + [ + Project.id_in(source_project), + Project.id_in(target_project_ids) + ] + end + end + end +end diff --git a/app/models/ci/job_token/outbound_scope.rb b/app/models/ci/job_token/outbound_scope.rb new file mode 100644 index 00000000000000..daede2a0309738 --- /dev/null +++ b/app/models/ci/job_token/outbound_scope.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Ci + module JobToken + class OutboundScope + attr_reader :source_project + + def initialize(source_project) + @source_project = source_project + end + + def includes?(target_project) + # if the setting is disabled any project is considered to be in scope. + return true unless source_project.ci_outbound_job_token_scope_enabled? + + self_referential?(target_project) || added?(target_project) + end + + def all_projects + Project.from_union(target_projects, remove_duplicates: false) + end + + private + + def self_referential?(target_project) + target_project.id == source_project.id + end + + def added?(target_project) + Ci::JobToken::ProjectScopeLink + .added_project(source_project, target_project) + .outbound + .exists? + end + + def target_project_ids + Ci::JobToken::ProjectScopeLink + .from_project(source_project) + .outbound + .pluck(:target_project_id) + end + + def target_projects + [ + Project.id_in(source_project), + Project.id_in(target_project_ids) + ] + end + end + end +end diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb index 3fdf07123e6cfb..d5628b30485622 100644 --- a/app/models/ci/job_token/project_scope_link.rb +++ b/app/models/ci/job_token/project_scope_link.rb @@ -14,6 +14,10 @@ class ProjectScopeLink < Ci::ApplicationRecord scope :from_project, ->(project) { where(source_project: project) } scope :to_project, ->(project) { where(target_project: project) } + scope :added_project, ->(source_project, target_project) do + where(source_project: source_project, target_project: target_project) + end + scope :outbound, ->(project) { where(direction: :outbound) } validates :source_project, presence: true validates :target_project, presence: true diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index 1aa49b95201aa6..16e8d26fd7a03e 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -1,49 +1,47 @@ # frozen_string_literal: true # This model represents the surface where a CI_JOB_TOKEN can be used. -# A Scope is initialized with the project that the job token belongs to, -# and indicates what are all the other projects that the token could access. # -# By default a job token can only access its own project, which is the same -# project that defines the scope. -# By adding ScopeLinks to the scope we can allow other projects to be accessed -# by the job token. This works as an allowlist of projects for a job token. +# A scope is initialized with a source project. +# +# Projects can be added to the scope by adding ScopeLinks to +# create an allowlist of projects. +# +# Projects in the outbound allowlist can be accessed via the token +# in the source project. +# +# Projects in the inbound allowlist can use their token to access +# the source project. +# +# CI_JOB_TOKEN should be considered untrusted without these features enabled. # -# If a project is not included in the scope we should not allow the job user -# to access it since operations using CI_JOB_TOKEN should be considered untrusted. module Ci module JobToken class Scope attr_reader :source_project - def initialize(project) - @source_project = project + def initialize(source_project) + @source_project = source_project end def includes?(target_project) - # if the setting is disabled any project is considered to be in scope. - return true unless source_project.ci_outbound_job_token_scope_enabled? - - target_project.id == source_project.id || - Ci::JobToken::ProjectScopeLink.from_project(source_project).to_project(target_project).exists? + outbound_scope.includes?(target_project) end def all_projects - Project.from_union(target_projects, remove_duplicates: false) + outbound_scope.all_projects end private - def target_project_ids - Ci::JobToken::ProjectScopeLink.from_project(source_project).pluck(:target_project_id) + def outbound_scope + Ci::JobToken::OutboundScope.new(source_project) end - def target_projects - [ - Project.id_in(source_project), - Project.id_in(target_project_ids) - ] + # TODO(https://gitlab.com/gitlab-org/gitlab/-/issues/346298): Use in this class + def inbound_scope + Ci::JobToken::InboundScope.new(source_project) end end end diff --git a/spec/models/ci/job_token/inbound_scope_spec.rb b/spec/models/ci/job_token/inbound_scope_spec.rb new file mode 100644 index 00000000000000..7d647ff715eac9 --- /dev/null +++ b/spec/models/ci/job_token/inbound_scope_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::JobToken::InboundScope do + let_it_be(:source_project) { create(:project, ci_inbound_job_token_scope_enabled: true).tap(&:save!) } + + let(:scope) { described_class.new(source_project) } + + shared_context 'with scoped projects' do + let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } + let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } + let!(:unscoped_project1) { create(:project) } + let!(:unscoped_project2) { create(:project) } + + let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } + end + + describe '#all_projects' do + subject(:all_projects) { scope.all_projects } + + context 'when no projects are added to the scope' do + it 'returns the project defining the scope' do + expect(all_projects).to contain_exactly(source_project) + end + end + + context 'when projects are added to the scope' do + include_context 'with scoped projects' + + it 'returns all projects that can be accessed from a given scope' do + expect(subject).to contain_exactly(source_project, inbound_scoped_project) + end + end + end + + describe '#includes?' do + subject { scope.includes?(includes_project) } + + context 'without projects' do + context 'when param is in scope' do + let(:includes_project) { source_project } + + it { is_expected.to be_truthy } + end + end + + context 'with scoped projects' do + include_context 'with scoped projects' + + context 'when project is self refrential' do + let(:includes_project) { source_project } + + it { is_expected.to be_truthy } + end + + context 'when project is in inbound scope' do + let(:includes_project) { inbound_scoped_project } + + it { is_expected.to be_truthy } + end + + context 'when project is in outbound scope' do + let(:includes_project) { outbound_scoped_project } + + it { is_expected.to be_falsey } + end + + context 'when project is linked to a different project' do + let(:includes_project) { unscoped_project1 } + + it { is_expected.to be_falsey } + end + + context 'when project is unlinked to any project' do + let(:includes_project) { unscoped_project2 } + + it { is_expected.to be_falsey } + end + + context 'when project scope setting is disabled' do + let(:includes_project) { unscoped_project1 } + + before do + source_project.ci_inbound_job_token_scope_enabled = false + end + + it 'considers any project to be part of the scope' do + expect(subject).to be_truthy + end + end + + context 'when feature flag is disabled' do + let(:includes_project) { unscoped_project1 } + + before do + stub_feature_flags(ci_inbound_job_token_scope: false) + end + + it 'considers any project to be part of the scope' do + expect(subject).to be_truthy + end + end + end + end + + private + + def create_scoped_project(source_project, direction: 0) + create(:project).tap do |scoped_project| + create( + :ci_job_token_project_scope_link, + source_project: source_project, + target_project: scoped_project, + direction: direction + ) + end + end +end diff --git a/spec/models/ci/job_token/outbound_scope_spec.rb b/spec/models/ci/job_token/outbound_scope_spec.rb new file mode 100644 index 00000000000000..ba750ba93fae89 --- /dev/null +++ b/spec/models/ci/job_token/outbound_scope_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::JobToken::OutboundScope do + let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } + + let(:scope) { described_class.new(source_project) } + + shared_context 'with scoped projects' do + let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } + let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } + let!(:unscoped_project1) { create(:project) } + let!(:unscoped_project2) { create(:project) } + + let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } + end + + describe '#all_projects' do + subject(:all_projects) { scope.all_projects } + + context 'when no projects are added to the scope' do + it 'returns the project defining the scope' do + expect(all_projects).to contain_exactly(source_project) + end + end + + context 'when projects are added to the scope' do + include_context 'with scoped projects' + + it 'returns all projects that can be accessed from a given scope' do + expect(subject).to contain_exactly(source_project, outbound_scoped_project) + end + end + end + + describe '#includes?' do + subject { scope.includes?(includes_project) } + + context 'without scoped projects' do + context 'when self referential' do + let(:includes_project) { source_project } + + it { is_expected.to be_truthy } + end + end + + context 'with scoped projects' do + include_context 'with scoped projects' + + context 'when project is in outbound scope' do + let(:includes_project) { outbound_scoped_project } + + it { is_expected.to be_truthy } + end + + context 'when project is in inbound scope' do + let(:includes_project) { inbound_scoped_project } + + it { is_expected.to be_falsey } + end + + context 'when project is linked to a different project' do + let(:includes_project) { unscoped_project1 } + + it { is_expected.to be_falsey } + end + + context 'when param is a project unlinked to any project' do + let(:includes_project) { unscoped_project2 } + + it { is_expected.to be_falsey } + end + + context 'when project scope setting is disabled' do + let(:includes_project) { unscoped_project1 } + + before do + source_project.ci_outbound_job_token_scope_enabled = false + end + + it 'considers any project to be part of the scope' do + expect(subject).to be_truthy + end + end + end + end + + private + + def create_scoped_project(source_project, direction: 0) + create(:project).tap do |scoped_project| + create( + :ci_job_token_project_scope_link, + source_project: source_project, + target_project: scoped_project, + direction: direction + ) + end + end +end diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index 1e3f6d044d2690..b6d6bf42857173 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -3,57 +3,80 @@ require 'spec_helper' RSpec.describe Ci::JobToken::Scope do - let_it_be(:project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } + let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } - let(:scope) { described_class.new(project) } + let(:scope) { described_class.new(source_project) } + + shared_context 'with scoped projects' do + let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } + let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } + let!(:unscoped_project1) { create(:project) } + let!(:unscoped_project2) { create(:project) } + + let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } + end describe '#all_projects' do subject(:all_projects) { scope.all_projects } context 'when no projects are added to the scope' do it 'returns the project defining the scope' do - expect(all_projects).to contain_exactly(project) + expect(all_projects).to contain_exactly(source_project) end end - context 'when other projects are added to the scope' do - let_it_be(:scoped_project) { create(:project) } - let_it_be(:unscoped_project) { create(:project) } - - let!(:link_in_scope) { create(:ci_job_token_project_scope_link, source_project: project, target_project: scoped_project) } - let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project) } + context 'when projects are added to the scope' do + include_context 'with scoped projects' it 'returns all projects that can be accessed from a given scope' do - expect(subject).to contain_exactly(project, scoped_project) + expect(subject).to contain_exactly(source_project, outbound_scoped_project) end end end describe '#includes?' do - subject { scope.includes?(target_project) } + subject { scope.includes?(includes_project) } - context 'when param is the project defining the scope' do - let(:target_project) { project } + context 'without scoped projects' do + context 'when self referential' do + let(:includes_project) { source_project } - it { is_expected.to be_truthy } + it { is_expected.to be_truthy } + end end - context 'when param is a project in scope' do - let(:target_link) { create(:ci_job_token_project_scope_link, source_project: project) } - let(:target_project) { target_link.target_project } + context 'with scoped projects' do + include_context 'with scoped projects' - it { is_expected.to be_truthy } - end + context 'when project is in outbound scope' do + let(:includes_project) { outbound_scoped_project } + + it { is_expected.to be_truthy } + end + + context 'when project is in inbound scope' do + let(:includes_project) { inbound_scoped_project } + + it { is_expected.to be_falsey } + end + + context 'when project is linked to a different project' do + let(:includes_project) { unscoped_project1 } - context 'when param is a project in another scope' do - let(:scope_link) { create(:ci_job_token_project_scope_link) } - let(:target_project) { scope_link.target_project } + it { is_expected.to be_falsey } + end - it { is_expected.to be_falsey } + context 'when param is a project unlinked to any project' do + let(:includes_project) { unscoped_project2 } + + it { is_expected.to be_falsey } + end context 'when project scope setting is disabled' do + let(:includes_project) { unscoped_project1 } + before do - project.ci_outbound_job_token_scope_enabled = false + source_project.ci_outbound_job_token_scope_enabled = false end it 'considers any project to be part of the scope' do @@ -62,4 +85,17 @@ end end end + + private + + def create_scoped_project(source_project, direction: 0) + create(:project).tap do |scoped_project| + create( + :ci_job_token_project_scope_link, + source_project: source_project, + target_project: scoped_project, + direction: direction + ) + end + end end -- GitLab From 9f228d0cab2511e7640e70b468ef034f6cd95bff Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Wed, 2 Nov 2022 16:37:14 -0400 Subject: [PATCH 02/12] Add a spec for the project link scope --- app/models/ci/job_token/project_scope_link.rb | 1 - .../ci/job_token/project_scope_link_spec.rb | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb index d5628b30485622..a0871a5cc97085 100644 --- a/app/models/ci/job_token/project_scope_link.rb +++ b/app/models/ci/job_token/project_scope_link.rb @@ -17,7 +17,6 @@ class ProjectScopeLink < Ci::ApplicationRecord scope :added_project, ->(source_project, target_project) do where(source_project: source_project, target_project: target_project) end - scope :outbound, ->(project) { where(direction: :outbound) } validates :source_project, presence: true validates :target_project, presence: true diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb index 6f428d1aa8b648..994d517a755e6a 100644 --- a/spec/models/ci/job_token/project_scope_link_spec.rb +++ b/spec/models/ci/job_token/project_scope_link_spec.rb @@ -72,6 +72,22 @@ end end + describe '.added_project' do + subject { described_class.added_project(project, target_project) } + + let!(:target_project) { create(:project) } + let!(:added) { create(:ci_job_token_project_scope_link, source_project: project, target_project: target_project) } + let!(:not_added_1) { create(:ci_job_token_project_scope_link, target_project: project) } + let!(:not_added_2) { create(:ci_job_token_project_scope_link, source_project: project) } + let!(:not_added_3) do + create(:ci_job_token_project_scope_link, source_project: target_project, target_project: project) + end + + it 'returns only the links added to the source' do + expect(subject).to contain_exactly(added) + end + end + describe '.for_source_and_target' do let_it_be(:link) { create(:ci_job_token_project_scope_link, source_project: project) } -- GitLab From 4fa578671c2328b64a33d5b4ea64cd74b4bba1d2 Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Wed, 2 Nov 2022 17:04:56 -0400 Subject: [PATCH 03/12] Remove method to appease undercoverge job --- app/models/ci/job_token/scope.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index 16e8d26fd7a03e..be62cd416039b4 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -38,11 +38,6 @@ def all_projects def outbound_scope Ci::JobToken::OutboundScope.new(source_project) end - - # TODO(https://gitlab.com/gitlab-org/gitlab/-/issues/346298): Use in this class - def inbound_scope - Ci::JobToken::InboundScope.new(source_project) - end end end end -- GitLab From dfcfc1402972bbe6c46e1cc556317538f287c3bb Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Thu, 3 Nov 2022 13:36:41 -0400 Subject: [PATCH 04/12] Code review feedback and add Allowlist class - Use Allowlist to DRY up outbound and inbound scopes - Add note about using pluck instead of select - remove un-needed .tap(&:save) call --- app/models/ci/job_token/allowlist.rb | 51 ++++++ app/models/ci/job_token/inbound_scope.rb | 29 +-- app/models/ci/job_token/outbound_scope.rb | 34 +--- app/models/ci/job_token/project_scope_link.rb | 7 +- spec/models/ci/job_token/allowlist_spec.rb | 165 ++++++++++++++++++ .../models/ci/job_token/inbound_scope_spec.rb | 2 +- .../ci/job_token/outbound_scope_spec.rb | 4 +- .../ci/job_token/project_scope_link_spec.rb | 24 +-- spec/models/ci/job_token/scope_spec.rb | 2 +- 9 files changed, 237 insertions(+), 81 deletions(-) create mode 100644 app/models/ci/job_token/allowlist.rb create mode 100644 spec/models/ci/job_token/allowlist_spec.rb diff --git a/app/models/ci/job_token/allowlist.rb b/app/models/ci/job_token/allowlist.rb new file mode 100644 index 00000000000000..748661b0c565a0 --- /dev/null +++ b/app/models/ci/job_token/allowlist.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +module Ci + module JobToken + class Allowlist + def initialize(source_project, direction:) + @source_project = source_project + @direction = direction + end + + def includes?(target_project) + self_referential?(target_project) || added?(target_project) + end + + def all_projects + Project.from_union(target_projects, remove_duplicates: false) + end + + private + + def self_referential?(target_project) + target_project.id == @source_project.id + end + + def added?(target_project) + source_links + .with_target(target_project) + .where(direction: @direction) + .exists? + end + + def target_project_ids + source_links + .where(direction: @direction) + # pluck needed to avoid ci and main db join + .pluck(:target_project_id) + end + + def source_links + Ci::JobToken::ProjectScopeLink + .with_source(@source_project) + end + + def target_projects + [ + Project.id_in(@source_project), + Project.id_in(target_project_ids) + ] + end + end + end +end diff --git a/app/models/ci/job_token/inbound_scope.rb b/app/models/ci/job_token/inbound_scope.rb index 9808b3261eb1ab..08da0e616ff246 100644 --- a/app/models/ci/job_token/inbound_scope.rb +++ b/app/models/ci/job_token/inbound_scope.rb @@ -15,38 +15,17 @@ def includes?(target_project) # if the setting is disabled any project is considered to be in scope. return true unless source_project.ci_inbound_job_token_scope_enabled? - self_referential?(target_project) || added?(target_project) + allowlist.includes?(target_project) end def all_projects - Project.from_union(target_projects, remove_duplicates: false) + allowlist.all_projects end private - def self_referential?(target_project) - target_project.id == source_project.id - end - - def added?(target_project) - Ci::JobToken::ProjectScopeLink - .added_project(source_project, target_project) - .inbound - .exists? - end - - def target_project_ids - Ci::JobToken::ProjectScopeLink - .from_project(source_project) - .inbound - .pluck(:target_project_id) - end - - def target_projects - [ - Project.id_in(source_project), - Project.id_in(target_project_ids) - ] + def allowlist + Ci::JobToken::Allowlist.new(source_project, direction: :inbound) end end end diff --git a/app/models/ci/job_token/outbound_scope.rb b/app/models/ci/job_token/outbound_scope.rb index daede2a0309738..df20e7232f9a63 100644 --- a/app/models/ci/job_token/outbound_scope.rb +++ b/app/models/ci/job_token/outbound_scope.rb @@ -13,38 +13,18 @@ def includes?(target_project) # if the setting is disabled any project is considered to be in scope. return true unless source_project.ci_outbound_job_token_scope_enabled? - self_referential?(target_project) || added?(target_project) + allowlist.includes?(target_project) end - def all_projects - Project.from_union(target_projects, remove_duplicates: false) - end + delegate :all_projects, to: :allowlist + # def all_projects + # allowlist.all_projects + # end private - def self_referential?(target_project) - target_project.id == source_project.id - end - - def added?(target_project) - Ci::JobToken::ProjectScopeLink - .added_project(source_project, target_project) - .outbound - .exists? - end - - def target_project_ids - Ci::JobToken::ProjectScopeLink - .from_project(source_project) - .outbound - .pluck(:target_project_id) - end - - def target_projects - [ - Project.id_in(source_project), - Project.id_in(target_project_ids) - ] + def allowlist + Ci::JobToken::Allowlist.new(source_project, direction: :outbound) end end end diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb index a0871a5cc97085..b784f93651a2b1 100644 --- a/app/models/ci/job_token/project_scope_link.rb +++ b/app/models/ci/job_token/project_scope_link.rb @@ -12,11 +12,8 @@ class ProjectScopeLink < Ci::ApplicationRecord belongs_to :target_project, class_name: 'Project' belongs_to :added_by, class_name: 'User' - scope :from_project, ->(project) { where(source_project: project) } - scope :to_project, ->(project) { where(target_project: project) } - scope :added_project, ->(source_project, target_project) do - where(source_project: source_project, target_project: target_project) - end + scope :with_source, ->(project) { where(source_project: project) } + scope :with_target, ->(project) { where(target_project: project) } validates :source_project, presence: true validates :target_project, presence: true diff --git a/spec/models/ci/job_token/allowlist_spec.rb b/spec/models/ci/job_token/allowlist_spec.rb new file mode 100644 index 00000000000000..2f35665183ba55 --- /dev/null +++ b/spec/models/ci/job_token/allowlist_spec.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::JobToken::Allowlist do + using RSpec::Parameterized::TableSyntax + + let_it_be(:source_project) { create(:project) } + + let(:scope) { described_class.new(source_project, direction: direction) } + + let(:direction) { :outbound } + + shared_context 'with scoped projects' do + let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } + let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } + let!(:unscoped_project1) { create(:project) } + let!(:unscoped_project2) { create(:project) } + + let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } + end + + describe '#projects' do + subject(:all_projects) { scope.all_projects } + + context 'when no projects are added to the scope' do + [:inbound, :outbound].each do |d| + let(:direction) { d } + + it 'returns the project defining the scope' do + expect(all_projects).to contain_exactly(source_project) + end + end + end + + context 'when projects are added to the scope' do + include_context 'with scoped projects' + + where(:direction, :result) do + :outbound | ref(:outbound_scoped_project) + :inbound | ref(:inbound_scoped_project) + end + + with_them do + it 'returns all projects that can be accessed from a given scope' do + expect(subject).to contain_exactly(source_project, result) + end + end + end + end + + describe '#includes?' do + subject { scope.includes?(includes_project) } + + context 'without projects' do + context 'when project is self referential' do + where(:direction, :result) do + :outbound | true + :inbound | true + end + + with_them do + let(:includes_project) { source_project } + + it { is_expected.to be result } + end + end + + context 'when project is not self referential' do + where(:direction, :result) do + :outbound | false + :inbound | false + end + + with_them do + let(:includes_project) { build(:project) } + + it { is_expected.to be result } + end + end + end + + context 'with scoped projects' do + include_context 'with scoped projects' + + context 'when project is self referential' do + where(:direction, :result) do + :outbound | true + :inbound | true + end + + with_them do + let(:includes_project) { source_project } + + it { is_expected.to be result } + end + end + + context 'when project is in inbound scope' do + where(:direction, :result) do + :outbound | false + :inbound | true + end + + with_them do + let(:includes_project) { inbound_scoped_project } + + it { is_expected.to be result } + end + end + + context 'when project is in outbound scope' do + where(:direction, :result) do + :outbound | true + :inbound | false + end + + with_them do + let(:includes_project) { outbound_scoped_project } + + it { is_expected.to be result } + end + end + + context 'when project is linked to a different project' do + where(:direction, :result) do + :outbound | false + :inbound | false + end + + with_them do + let(:includes_project) { unscoped_project1 } + + it { is_expected.to be result } + end + end + + context 'when project is unlinked to any project' do + where(:direction, :result) do + :outbound | false + :inbound | false + end + + with_them do + let(:includes_project) { unscoped_project2 } + + it { is_expected.to be result } + end + end + end + end + + private + + def create_scoped_project(source_project, direction: 0) + create(:project).tap do |scoped_project| + create( + :ci_job_token_project_scope_link, + source_project: source_project, + target_project: scoped_project, + direction: direction + ) + end + end +end diff --git a/spec/models/ci/job_token/inbound_scope_spec.rb b/spec/models/ci/job_token/inbound_scope_spec.rb index 7d647ff715eac9..5bb51cc33ec271 100644 --- a/spec/models/ci/job_token/inbound_scope_spec.rb +++ b/spec/models/ci/job_token/inbound_scope_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobToken::InboundScope do - let_it_be(:source_project) { create(:project, ci_inbound_job_token_scope_enabled: true).tap(&:save!) } + let_it_be(:source_project) { create(:project, ci_inbound_job_token_scope_enabled: true) } let(:scope) { described_class.new(source_project) } diff --git a/spec/models/ci/job_token/outbound_scope_spec.rb b/spec/models/ci/job_token/outbound_scope_spec.rb index ba750ba93fae89..879177a5ed9972 100644 --- a/spec/models/ci/job_token/outbound_scope_spec.rb +++ b/spec/models/ci/job_token/outbound_scope_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobToken::OutboundScope do - let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } + let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } let(:scope) { described_class.new(source_project) } @@ -66,7 +66,7 @@ it { is_expected.to be_falsey } end - context 'when param is a project unlinked to any project' do + context 'when project is unlinked to any project' do let(:includes_project) { unscoped_project2 } it { is_expected.to be_falsey } diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb index 994d517a755e6a..e2aba5b59fa869 100644 --- a/spec/models/ci/job_token/project_scope_link_spec.rb +++ b/spec/models/ci/job_token/project_scope_link_spec.rb @@ -50,8 +50,8 @@ end end - describe '.from_project' do - subject { described_class.from_project(project) } + describe '.with_source' do + subject { described_class.with_source(project) } let!(:source_link) { create(:ci_job_token_project_scope_link, source_project: project) } let!(:target_link) { create(:ci_job_token_project_scope_link, target_project: project) } @@ -61,8 +61,8 @@ end end - describe '.to_project' do - subject { described_class.to_project(project) } + describe '.with_target' do + subject { described_class.with_target(project) } let!(:source_link) { create(:ci_job_token_project_scope_link, source_project: project) } let!(:target_link) { create(:ci_job_token_project_scope_link, target_project: project) } @@ -72,22 +72,6 @@ end end - describe '.added_project' do - subject { described_class.added_project(project, target_project) } - - let!(:target_project) { create(:project) } - let!(:added) { create(:ci_job_token_project_scope_link, source_project: project, target_project: target_project) } - let!(:not_added_1) { create(:ci_job_token_project_scope_link, target_project: project) } - let!(:not_added_2) { create(:ci_job_token_project_scope_link, source_project: project) } - let!(:not_added_3) do - create(:ci_job_token_project_scope_link, source_project: target_project, target_project: project) - end - - it 'returns only the links added to the source' do - expect(subject).to contain_exactly(added) - end - end - describe '.for_source_and_target' do let_it_be(:link) { create(:ci_job_token_project_scope_link, source_project: project) } diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index b6d6bf42857173..ac23043492e77f 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobToken::Scope do - let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } + let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } let(:scope) { described_class.new(source_project) } -- GitLab From 625526acd31055307dda0d4485d1e9b2b09046cd Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Mon, 7 Nov 2022 14:49:35 -0500 Subject: [PATCH 05/12] DRY using shared context and fix perf - DRY the setup of scoped projects by using a shared context across specs - Use let_it_be and build_stubbed to speed up the scop specs --- app/models/ci/job_token/inbound_scope.rb | 4 +--- app/models/ci/job_token/outbound_scope.rb | 3 --- spec/models/ci/job_token/allowlist_spec.rb | 22 ----------------- .../models/ci/job_token/inbound_scope_spec.rb | 22 ----------------- .../ci/job_token/outbound_scope_spec.rb | 22 ----------------- spec/models/ci/job_token/scope_spec.rb | 24 +------------------ .../models/ci/job_token_scope.rb | 21 ++++++++++++++++ 7 files changed, 23 insertions(+), 95 deletions(-) create mode 100644 spec/support/shared_contexts/models/ci/job_token_scope.rb diff --git a/app/models/ci/job_token/inbound_scope.rb b/app/models/ci/job_token/inbound_scope.rb index 08da0e616ff246..acebe49ca9b512 100644 --- a/app/models/ci/job_token/inbound_scope.rb +++ b/app/models/ci/job_token/inbound_scope.rb @@ -18,9 +18,7 @@ def includes?(target_project) allowlist.includes?(target_project) end - def all_projects - allowlist.all_projects - end + delegate :all_projects, to: :allowlist private diff --git a/app/models/ci/job_token/outbound_scope.rb b/app/models/ci/job_token/outbound_scope.rb index df20e7232f9a63..9c8fee973e3e1c 100644 --- a/app/models/ci/job_token/outbound_scope.rb +++ b/app/models/ci/job_token/outbound_scope.rb @@ -17,9 +17,6 @@ def includes?(target_project) end delegate :all_projects, to: :allowlist - # def all_projects - # allowlist.all_projects - # end private diff --git a/spec/models/ci/job_token/allowlist_spec.rb b/spec/models/ci/job_token/allowlist_spec.rb index 2f35665183ba55..72226d82c70b97 100644 --- a/spec/models/ci/job_token/allowlist_spec.rb +++ b/spec/models/ci/job_token/allowlist_spec.rb @@ -11,15 +11,6 @@ let(:direction) { :outbound } - shared_context 'with scoped projects' do - let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } - let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } - let!(:unscoped_project1) { create(:project) } - let!(:unscoped_project2) { create(:project) } - - let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } - end - describe '#projects' do subject(:all_projects) { scope.all_projects } @@ -149,17 +140,4 @@ end end end - - private - - def create_scoped_project(source_project, direction: 0) - create(:project).tap do |scoped_project| - create( - :ci_job_token_project_scope_link, - source_project: source_project, - target_project: scoped_project, - direction: direction - ) - end - end end diff --git a/spec/models/ci/job_token/inbound_scope_spec.rb b/spec/models/ci/job_token/inbound_scope_spec.rb index 5bb51cc33ec271..1392282992e3cf 100644 --- a/spec/models/ci/job_token/inbound_scope_spec.rb +++ b/spec/models/ci/job_token/inbound_scope_spec.rb @@ -7,15 +7,6 @@ let(:scope) { described_class.new(source_project) } - shared_context 'with scoped projects' do - let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } - let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } - let!(:unscoped_project1) { create(:project) } - let!(:unscoped_project2) { create(:project) } - - let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } - end - describe '#all_projects' do subject(:all_projects) { scope.all_projects } @@ -103,17 +94,4 @@ end end end - - private - - def create_scoped_project(source_project, direction: 0) - create(:project).tap do |scoped_project| - create( - :ci_job_token_project_scope_link, - source_project: source_project, - target_project: scoped_project, - direction: direction - ) - end - end end diff --git a/spec/models/ci/job_token/outbound_scope_spec.rb b/spec/models/ci/job_token/outbound_scope_spec.rb index 879177a5ed9972..e855e975c4b2b8 100644 --- a/spec/models/ci/job_token/outbound_scope_spec.rb +++ b/spec/models/ci/job_token/outbound_scope_spec.rb @@ -7,15 +7,6 @@ let(:scope) { described_class.new(source_project) } - shared_context 'with scoped projects' do - let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } - let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } - let!(:unscoped_project1) { create(:project) } - let!(:unscoped_project2) { create(:project) } - - let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } - end - describe '#all_projects' do subject(:all_projects) { scope.all_projects } @@ -85,17 +76,4 @@ end end end - - private - - def create_scoped_project(source_project, direction: 0) - create(:project).tap do |scoped_project| - create( - :ci_job_token_project_scope_link, - source_project: source_project, - target_project: scoped_project, - direction: direction - ) - end - end end diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index ac23043492e77f..9fe5decc1bc016 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -7,15 +7,6 @@ let(:scope) { described_class.new(source_project) } - shared_context 'with scoped projects' do - let!(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } - let!(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } - let!(:unscoped_project1) { create(:project) } - let!(:unscoped_project2) { create(:project) } - - let!(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } - end - describe '#all_projects' do subject(:all_projects) { scope.all_projects } @@ -66,7 +57,7 @@ it { is_expected.to be_falsey } end - context 'when param is a project unlinked to any project' do + context 'when project is unlinked to a project' do let(:includes_project) { unscoped_project2 } it { is_expected.to be_falsey } @@ -85,17 +76,4 @@ end end end - - private - - def create_scoped_project(source_project, direction: 0) - create(:project).tap do |scoped_project| - create( - :ci_job_token_project_scope_link, - source_project: source_project, - target_project: scoped_project, - direction: direction - ) - end - end end diff --git a/spec/support/shared_contexts/models/ci/job_token_scope.rb b/spec/support/shared_contexts/models/ci/job_token_scope.rb new file mode 100644 index 00000000000000..d33c954b34ac0c --- /dev/null +++ b/spec/support/shared_contexts/models/ci/job_token_scope.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with scoped projects' do + let_it_be(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } + let_it_be(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } + let_it_be(:unscoped_project1) { build_stubbed(:project) } + let_it_be(:unscoped_project2) { build_stubbed(:project) } + + let_it_be(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } + + def create_scoped_project(source_project, direction:) + create(:project).tap do |scoped_project| + create( + :ci_job_token_project_scope_link, + source_project: source_project, + target_project: scoped_project, + direction: direction + ) + end + end +end -- GitLab From acf0c5b05d87489a72ccae03ce860dcb8bd23b3e Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Tue, 8 Nov 2022 11:20:02 -0500 Subject: [PATCH 06/12] Respond to code feedback - paramaterize the tables - remove attr_reader - remove inbound_scope for now since we cant use includes for the access check and won't use it otherwise --- app/models/ci/job_token/inbound_scope.rb | 30 ------ app/models/ci/job_token/outbound_scope.rb | 6 +- app/models/ci/job_token/scope.rb | 4 +- .../models/ci/job_token/inbound_scope_spec.rb | 97 ------------------- .../ci/job_token/outbound_scope_spec.rb | 46 +++++---- 5 files changed, 25 insertions(+), 158 deletions(-) delete mode 100644 app/models/ci/job_token/inbound_scope.rb delete mode 100644 spec/models/ci/job_token/inbound_scope_spec.rb diff --git a/app/models/ci/job_token/inbound_scope.rb b/app/models/ci/job_token/inbound_scope.rb deleted file mode 100644 index acebe49ca9b512..00000000000000 --- a/app/models/ci/job_token/inbound_scope.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Ci - module JobToken - class InboundScope - attr_reader :source_project - - def initialize(source_project) - @source_project = source_project - end - - def includes?(target_project) - # if the flag is disabled any project is considered to be in scope. - return true unless Feature.enabled?(:ci_inbound_job_token_scope, source_project) - # if the setting is disabled any project is considered to be in scope. - return true unless source_project.ci_inbound_job_token_scope_enabled? - - allowlist.includes?(target_project) - end - - delegate :all_projects, to: :allowlist - - private - - def allowlist - Ci::JobToken::Allowlist.new(source_project, direction: :inbound) - end - end - end -end diff --git a/app/models/ci/job_token/outbound_scope.rb b/app/models/ci/job_token/outbound_scope.rb index 9c8fee973e3e1c..2b7454257b4f3c 100644 --- a/app/models/ci/job_token/outbound_scope.rb +++ b/app/models/ci/job_token/outbound_scope.rb @@ -3,15 +3,13 @@ module Ci module JobToken class OutboundScope - attr_reader :source_project - def initialize(source_project) @source_project = source_project end def includes?(target_project) # if the setting is disabled any project is considered to be in scope. - return true unless source_project.ci_outbound_job_token_scope_enabled? + return true unless @source_project.ci_outbound_job_token_scope_enabled? allowlist.includes?(target_project) end @@ -21,7 +19,7 @@ def includes?(target_project) private def allowlist - Ci::JobToken::Allowlist.new(source_project, direction: :outbound) + Ci::JobToken::Allowlist.new(@source_project, direction: :outbound) end end end diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index be62cd416039b4..9697efc8cf7897 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -29,9 +29,7 @@ def includes?(target_project) outbound_scope.includes?(target_project) end - def all_projects - outbound_scope.all_projects - end + delegate :all_projects, to: :outbound_scope private diff --git a/spec/models/ci/job_token/inbound_scope_spec.rb b/spec/models/ci/job_token/inbound_scope_spec.rb deleted file mode 100644 index 1392282992e3cf..00000000000000 --- a/spec/models/ci/job_token/inbound_scope_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Ci::JobToken::InboundScope do - let_it_be(:source_project) { create(:project, ci_inbound_job_token_scope_enabled: true) } - - let(:scope) { described_class.new(source_project) } - - describe '#all_projects' do - subject(:all_projects) { scope.all_projects } - - context 'when no projects are added to the scope' do - it 'returns the project defining the scope' do - expect(all_projects).to contain_exactly(source_project) - end - end - - context 'when projects are added to the scope' do - include_context 'with scoped projects' - - it 'returns all projects that can be accessed from a given scope' do - expect(subject).to contain_exactly(source_project, inbound_scoped_project) - end - end - end - - describe '#includes?' do - subject { scope.includes?(includes_project) } - - context 'without projects' do - context 'when param is in scope' do - let(:includes_project) { source_project } - - it { is_expected.to be_truthy } - end - end - - context 'with scoped projects' do - include_context 'with scoped projects' - - context 'when project is self refrential' do - let(:includes_project) { source_project } - - it { is_expected.to be_truthy } - end - - context 'when project is in inbound scope' do - let(:includes_project) { inbound_scoped_project } - - it { is_expected.to be_truthy } - end - - context 'when project is in outbound scope' do - let(:includes_project) { outbound_scoped_project } - - it { is_expected.to be_falsey } - end - - context 'when project is linked to a different project' do - let(:includes_project) { unscoped_project1 } - - it { is_expected.to be_falsey } - end - - context 'when project is unlinked to any project' do - let(:includes_project) { unscoped_project2 } - - it { is_expected.to be_falsey } - end - - context 'when project scope setting is disabled' do - let(:includes_project) { unscoped_project1 } - - before do - source_project.ci_inbound_job_token_scope_enabled = false - end - - it 'considers any project to be part of the scope' do - expect(subject).to be_truthy - end - end - - context 'when feature flag is disabled' do - let(:includes_project) { unscoped_project1 } - - before do - stub_feature_flags(ci_inbound_job_token_scope: false) - end - - it 'considers any project to be part of the scope' do - expect(subject).to be_truthy - end - end - end - end -end diff --git a/spec/models/ci/job_token/outbound_scope_spec.rb b/spec/models/ci/job_token/outbound_scope_spec.rb index e855e975c4b2b8..38651b6a13bff7 100644 --- a/spec/models/ci/job_token/outbound_scope_spec.rb +++ b/spec/models/ci/job_token/outbound_scope_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Ci::JobToken::OutboundScope do + using RSpec::Parameterized::TableSyntax + let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } let(:scope) { described_class.new(source_project) } @@ -39,39 +41,35 @@ context 'with scoped projects' do include_context 'with scoped projects' - context 'when project is in outbound scope' do - let(:includes_project) { outbound_scoped_project } - - it { is_expected.to be_truthy } + where(:includes_project, :result) do + ref(:source_project) | true + ref(:inbound_scoped_project) | false + ref(:outbound_scoped_project) | true + ref(:unscoped_project1) | false + ref(:unscoped_project2) | false end - context 'when project is in inbound scope' do - let(:includes_project) { inbound_scoped_project } - - it { is_expected.to be_falsey } - end - - context 'when project is linked to a different project' do - let(:includes_project) { unscoped_project1 } - - it { is_expected.to be_falsey } - end - - context 'when project is unlinked to any project' do - let(:includes_project) { unscoped_project2 } - - it { is_expected.to be_falsey } + with_them do + it { is_expected.to eq(result) } end context 'when project scope setting is disabled' do - let(:includes_project) { unscoped_project1 } - before do source_project.ci_outbound_job_token_scope_enabled = false end - it 'considers any project to be part of the scope' do - expect(subject).to be_truthy + where(:includes_project, :result) do + ref(:source_project) | true + ref(:inbound_scoped_project) | true + ref(:outbound_scoped_project) | true + ref(:unscoped_project1) | true + ref(:unscoped_project2) | true + end + + with_them do + it 'considers any project as part of the scope' do + is_expected.to eq(result) + end end end end -- GitLab From e1c29b115a9db340c269176abad8fb13b70c8748 Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Thu, 10 Nov 2022 10:54:07 -0500 Subject: [PATCH 07/12] Fix up some comments --- app/models/ci/job_token/scope.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index 9697efc8cf7897..7c3ad1c053a89f 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -5,12 +5,12 @@ # A scope is initialized with a source project. # # Projects can be added to the scope by adding ScopeLinks to -# create an allowlist of projects. +# create an allowlist of projects in either access direction (inbound, outbound). # -# Projects in the outbound allowlist can be accessed via the token +# Currently, Projects in the outbound allowlist can be accessed via the token # in the source project. # -# Projects in the inbound allowlist can use their token to access +# TODO(Issue #346298) Projects in the inbound allowlist can use their token to access # the source project. # # CI_JOB_TOKEN should be considered untrusted without these features enabled. -- GitLab From 8cc8907d1a5d58acaa8f88b43681a5bded86e555 Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Wed, 30 Nov 2022 17:08:53 -0500 Subject: [PATCH 08/12] Change target_project and source_project names At a high level these are the current project and the accessed project and the target and source terminology now applies only to the allowlist targeting that project to be added. --- app/models/ci/job_token/scope.rb | 12 ++++++------ spec/finders/ci/auth_job_finder_spec.rb | 2 +- spec/lib/gitlab/auth/auth_finders_spec.rb | 4 ++-- .../shared_contexts/models/ci/job_token_scope.rb | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index 7c3ad1c053a89f..04b7c735379e08 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -19,14 +19,14 @@ module Ci module JobToken class Scope - attr_reader :source_project + attr_reader :current_project - def initialize(source_project) - @source_project = source_project + def initialize(current_project) + @current_project = current_project end - def includes?(target_project) - outbound_scope.includes?(target_project) + def includes?(accessed_project) + outbound_scope.includes?(accessed_project) end delegate :all_projects, to: :outbound_scope @@ -34,7 +34,7 @@ def includes?(target_project) private def outbound_scope - Ci::JobToken::OutboundScope.new(source_project) + Ci::JobToken::OutboundScope.new(current_project) end end end diff --git a/spec/finders/ci/auth_job_finder_spec.rb b/spec/finders/ci/auth_job_finder_spec.rb index 0a326699875a0c..54d60264721797 100644 --- a/spec/finders/ci/auth_job_finder_spec.rb +++ b/spec/finders/ci/auth_job_finder_spec.rb @@ -68,7 +68,7 @@ it 'sets ci_job_token_scope on the job user', :aggregate_failures do expect(subject).to eq(job) expect(subject.user).to be_from_ci_job_token - expect(subject.user.ci_job_token_scope.source_project).to eq(job.project) + expect(subject.user.ci_job_token_scope.current_project).to eq(job.project) end end end diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 9283c31a207918..484b4702497e5a 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -69,7 +69,7 @@ def set_bearer_token(token) expect(subject).to eq(user) expect(@current_authenticated_job).to eq job expect(subject).to be_from_ci_job_token - expect(subject.ci_job_token_scope.source_project).to eq(job.project) + expect(subject.ci_job_token_scope.current_project).to eq(job.project) end end @@ -100,7 +100,7 @@ def set_bearer_token(token) expect(subject).to eq(user) expect(@current_authenticated_job).to eq job expect(subject).to be_from_ci_job_token - expect(subject.ci_job_token_scope.source_project).to eq(job.project) + expect(subject.ci_job_token_scope.current_project).to eq(job.project) end else it 'returns nil' do diff --git a/spec/support/shared_contexts/models/ci/job_token_scope.rb b/spec/support/shared_contexts/models/ci/job_token_scope.rb index d33c954b34ac0c..51f671b139d69e 100644 --- a/spec/support/shared_contexts/models/ci/job_token_scope.rb +++ b/spec/support/shared_contexts/models/ci/job_token_scope.rb @@ -3,8 +3,8 @@ RSpec.shared_context 'with scoped projects' do let_it_be(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } let_it_be(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } - let_it_be(:unscoped_project1) { build_stubbed(:project) } - let_it_be(:unscoped_project2) { build_stubbed(:project) } + let_it_be(:unscoped_project1) { create(:project) } + let_it_be(:unscoped_project2) { create(:project) } let_it_be(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } -- GitLab From de63b32890eaa0c5d5ffe5a93d9901c06d84230f Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Thu, 1 Dec 2022 11:54:29 -0500 Subject: [PATCH 09/12] DRY allowlist spec with parameterized tables --- spec/models/ci/job_token/allowlist_spec.rb | 108 +++++---------------- 1 file changed, 24 insertions(+), 84 deletions(-) diff --git a/spec/models/ci/job_token/allowlist_spec.rb b/spec/models/ci/job_token/allowlist_spec.rb index 72226d82c70b97..6c1271f36fd545 100644 --- a/spec/models/ci/job_token/allowlist_spec.rb +++ b/spec/models/ci/job_token/allowlist_spec.rb @@ -2,13 +2,12 @@ require 'spec_helper' -RSpec.describe Ci::JobToken::Allowlist do +RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integration do using RSpec::Parameterized::TableSyntax let_it_be(:source_project) { create(:project) } let(:scope) { described_class.new(source_project, direction: direction) } - let(:direction) { :outbound } describe '#projects' do @@ -27,14 +26,14 @@ context 'when projects are added to the scope' do include_context 'with scoped projects' - where(:direction, :result) do + where(:direction, :additional_project) do :outbound | ref(:outbound_scoped_project) :inbound | ref(:inbound_scoped_project) end with_them do it 'returns all projects that can be accessed from a given scope' do - expect(subject).to contain_exactly(source_project, result) + expect(all_projects).to contain_exactly(source_project, additional_project) end end end @@ -43,29 +42,18 @@ describe '#includes?' do subject { scope.includes?(includes_project) } - context 'without projects' do - context 'when project is self referential' do - where(:direction, :result) do - :outbound | true - :inbound | true - end + context 'without scoped projects' do + let(:unscoped_project) { build(:project) } - with_them do - let(:includes_project) { source_project } - - it { is_expected.to be result } - end - end - - context 'when project is not self referential' do - where(:direction, :result) do - :outbound | false - :inbound | false + context 'when project is self referential' do + where(:includes_project, :direction, :result) do + ref(:source_project) | :outbound | true + ref(:source_project) | :inbound | true + ref(:unscoped_project) | :outbound | false + ref(:unscoped_project) | :inbound | false end with_them do - let(:includes_project) { build(:project) } - it { is_expected.to be result } end end @@ -74,69 +62,21 @@ context 'with scoped projects' do include_context 'with scoped projects' - context 'when project is self referential' do - where(:direction, :result) do - :outbound | true - :inbound | true - end - - with_them do - let(:includes_project) { source_project } - - it { is_expected.to be result } - end + where(:includes_project, :direction, :result) do + ref(:source_project) | :outbound | true + ref(:source_project) | :inbound | true + ref(:inbound_scoped_project) | :outbound | false + ref(:inbound_scoped_project) | :inbound | true + ref(:outbound_scoped_project) | :outbound | true + ref(:outbound_scoped_project) | :inbound | false + ref(:unscoped_project1) | :outbound | false + ref(:unscoped_project1) | :inbound | false + ref(:unscoped_project2) | :outbound | false + ref(:unscoped_project2) | :inbound | false end - context 'when project is in inbound scope' do - where(:direction, :result) do - :outbound | false - :inbound | true - end - - with_them do - let(:includes_project) { inbound_scoped_project } - - it { is_expected.to be result } - end - end - - context 'when project is in outbound scope' do - where(:direction, :result) do - :outbound | true - :inbound | false - end - - with_them do - let(:includes_project) { outbound_scoped_project } - - it { is_expected.to be result } - end - end - - context 'when project is linked to a different project' do - where(:direction, :result) do - :outbound | false - :inbound | false - end - - with_them do - let(:includes_project) { unscoped_project1 } - - it { is_expected.to be result } - end - end - - context 'when project is unlinked to any project' do - where(:direction, :result) do - :outbound | false - :inbound | false - end - - with_them do - let(:includes_project) { unscoped_project2 } - - it { is_expected.to be result } - end + with_them do + it { is_expected.to be result } end end end -- GitLab From 9a54a9cf911d7a239869a43e3b902636404b1dae Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Tue, 6 Dec 2022 13:19:02 -0500 Subject: [PATCH 10/12] Add feature categories to specs --- spec/finders/ci/auth_job_finder_spec.rb | 2 +- spec/models/ci/job_token/outbound_scope_spec.rb | 2 +- spec/models/ci/job_token/project_scope_link_spec.rb | 2 +- spec/models/ci/job_token/scope_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/finders/ci/auth_job_finder_spec.rb b/spec/finders/ci/auth_job_finder_spec.rb index 54d60264721797..73a65d0c5af4c3 100644 --- a/spec/finders/ci/auth_job_finder_spec.rb +++ b/spec/finders/ci/auth_job_finder_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::AuthJobFinder do +RSpec.describe Ci::AuthJobFinder, feature_category: :continuous_integration do let_it_be(:user, reload: true) { create(:user) } let_it_be(:job, reload: true) { create(:ci_build, status: :running, user: user) } diff --git a/spec/models/ci/job_token/outbound_scope_spec.rb b/spec/models/ci/job_token/outbound_scope_spec.rb index 38651b6a13bff7..a64b0ca5f92bd2 100644 --- a/spec/models/ci/job_token/outbound_scope_spec.rb +++ b/spec/models/ci/job_token/outbound_scope_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobToken::OutboundScope do +RSpec.describe Ci::JobToken::OutboundScope, feature_category: :continuous_integration do using RSpec::Parameterized::TableSyntax let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb index e2aba5b59fa869..91491733c444ba 100644 --- a/spec/models/ci/job_token/project_scope_link_spec.rb +++ b/spec/models/ci/job_token/project_scope_link_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobToken::ProjectScopeLink do +RSpec.describe Ci::JobToken::ProjectScopeLink, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let_it_be(:group) { create(:group) } diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index 9fe5decc1bc016..1d9e76095ee1ea 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobToken::Scope do +RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration do let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } let(:scope) { described_class.new(source_project) } -- GitLab From 9543663734fd36e8403abf46ef413db7780b9e30 Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Fri, 9 Dec 2022 16:48:16 -0500 Subject: [PATCH 11/12] Change scope to allowlist in spec --- spec/models/ci/job_token/allowlist_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/ci/job_token/allowlist_spec.rb b/spec/models/ci/job_token/allowlist_spec.rb index 6c1271f36fd545..a5bce669de1037 100644 --- a/spec/models/ci/job_token/allowlist_spec.rb +++ b/spec/models/ci/job_token/allowlist_spec.rb @@ -7,11 +7,11 @@ let_it_be(:source_project) { create(:project) } - let(:scope) { described_class.new(source_project, direction: direction) } + let(:allowlist) { described_class.new(source_project, direction: direction) } let(:direction) { :outbound } describe '#projects' do - subject(:all_projects) { scope.all_projects } + subject(:all_projects) { allowlist.all_projects } context 'when no projects are added to the scope' do [:inbound, :outbound].each do |d| @@ -40,7 +40,7 @@ end describe '#includes?' do - subject { scope.includes?(includes_project) } + subject { allowlist.includes?(includes_project) } context 'without scoped projects' do let(:unscoped_project) { build(:project) } -- GitLab From fddecb289ca52509cd81486102528be9fcbf614e Mon Sep 17 00:00:00 2001 From: Allison Browne Date: Mon, 12 Dec 2022 16:28:03 -0500 Subject: [PATCH 12/12] Code review feedback --- app/models/ci/job_token/allowlist.rb | 23 ++---- app/models/ci/job_token/outbound_scope.rb | 26 ------- app/models/ci/job_token/scope.rb | 34 ++++++-- app/policies/project_policy.rb | 2 +- ee/spec/requests/api/ci/triggers_spec.rb | 4 +- spec/models/ci/job_token/allowlist_spec.rb | 28 ++++--- .../ci/job_token/outbound_scope_spec.rb | 77 ------------------- spec/models/ci/job_token/scope_spec.rb | 4 +- .../ci/job_token_scope/add_project_spec.rb | 2 +- .../ci/job_token_scope/remove_project_spec.rb | 2 +- 10 files changed, 53 insertions(+), 149 deletions(-) delete mode 100644 app/models/ci/job_token/outbound_scope.rb delete mode 100644 spec/models/ci/job_token/outbound_scope_spec.rb diff --git a/app/models/ci/job_token/allowlist.rb b/app/models/ci/job_token/allowlist.rb index 748661b0c565a0..9e9a0a68ebd438 100644 --- a/app/models/ci/job_token/allowlist.rb +++ b/app/models/ci/job_token/allowlist.rb @@ -8,38 +8,29 @@ def initialize(source_project, direction:) end def includes?(target_project) - self_referential?(target_project) || added?(target_project) + source_links + .with_target(target_project) + .exists? end - def all_projects + def projects Project.from_union(target_projects, remove_duplicates: false) end private - def self_referential?(target_project) - target_project.id == @source_project.id - end - - def added?(target_project) - source_links - .with_target(target_project) + def source_links + Ci::JobToken::ProjectScopeLink + .with_source(@source_project) .where(direction: @direction) - .exists? end def target_project_ids source_links - .where(direction: @direction) # pluck needed to avoid ci and main db join .pluck(:target_project_id) end - def source_links - Ci::JobToken::ProjectScopeLink - .with_source(@source_project) - end - def target_projects [ Project.id_in(@source_project), diff --git a/app/models/ci/job_token/outbound_scope.rb b/app/models/ci/job_token/outbound_scope.rb deleted file mode 100644 index 2b7454257b4f3c..00000000000000 --- a/app/models/ci/job_token/outbound_scope.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Ci - module JobToken - class OutboundScope - def initialize(source_project) - @source_project = source_project - end - - def includes?(target_project) - # if the setting is disabled any project is considered to be in scope. - return true unless @source_project.ci_outbound_job_token_scope_enabled? - - allowlist.includes?(target_project) - end - - delegate :all_projects, to: :allowlist - - private - - def allowlist - Ci::JobToken::Allowlist.new(@source_project, direction: :outbound) - end - end - end -end diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index 04b7c735379e08..e320c0f92d14eb 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -# This model represents the surface where a CI_JOB_TOKEN can be used. +# This model represents the scope of access for a CI_JOB_TOKEN. # -# A scope is initialized with a source project. +# A scope is initialized with a project. # # Projects can be added to the scope by adding ScopeLinks to # create an allowlist of projects in either access direction (inbound, outbound). # -# Currently, Projects in the outbound allowlist can be accessed via the token +# Currently, projects in the outbound allowlist can be accessed via the token # in the source project. # # TODO(Issue #346298) Projects in the inbound allowlist can use their token to access @@ -25,16 +25,34 @@ def initialize(current_project) @current_project = current_project end - def includes?(accessed_project) - outbound_scope.includes?(accessed_project) + def allows?(accessed_project) + self_referential?(accessed_project) || outbound_allows?(accessed_project) end - delegate :all_projects, to: :outbound_scope + def outbound_projects + outbound_allowlist.projects + end + + # Deprecated: use outbound_projects, TODO(Issue #346298) remove references to all_project + def all_projects + outbound_projects + end private - def outbound_scope - Ci::JobToken::OutboundScope.new(current_project) + def outbound_allows?(accessed_project) + # if the setting is disabled any project is considered to be in scope. + return true unless @current_project.ci_outbound_job_token_scope_enabled? + + outbound_allowlist.includes?(accessed_project) + end + + def outbound_allowlist + Ci::JobToken::Allowlist.new(@current_project, direction: :outbound) + end + + def self_referential?(accessed_project) + @current_project.id == accessed_project.id end end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index bfeb1a602ab71b..2155f84713e34b 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -121,7 +121,7 @@ class ProjectPolicy < BasePolicy desc "If user is authenticated via CI job token then the target project should be in scope" condition(:project_allowed_for_job_token) do - !@user&.from_ci_job_token? || @user.ci_job_token_scope.includes?(project) + !@user&.from_ci_job_token? || @user.ci_job_token_scope.allows?(project) end with_scope :subject diff --git a/ee/spec/requests/api/ci/triggers_spec.rb b/ee/spec/requests/api/ci/triggers_spec.rb index ef119871854288..5e5b9df0fb0a18 100644 --- a/ee/spec/requests/api/ci/triggers_spec.rb +++ b/ee/spec/requests/api/ci/triggers_spec.rb @@ -19,7 +19,7 @@ end before do - allow_next(Ci::JobToken::Scope).to receive(:includes?).with(project).and_return(true) + allow_next(Ci::JobToken::Scope).to receive(:allows?).with(project).and_return(true) end context 'without user' do @@ -80,7 +80,7 @@ context 'when project is not in the job token scope' do before do allow_next(Ci::JobToken::Scope) - .to receive(:includes?) + .to receive(:allows?) .with(project) .and_return(false) end diff --git a/spec/models/ci/job_token/allowlist_spec.rb b/spec/models/ci/job_token/allowlist_spec.rb index a5bce669de1037..45083d64393a77 100644 --- a/spec/models/ci/job_token/allowlist_spec.rb +++ b/spec/models/ci/job_token/allowlist_spec.rb @@ -11,14 +11,14 @@ let(:direction) { :outbound } describe '#projects' do - subject(:all_projects) { allowlist.all_projects } + subject(:projects) { allowlist.projects } context 'when no projects are added to the scope' do [:inbound, :outbound].each do |d| let(:direction) { d } it 'returns the project defining the scope' do - expect(all_projects).to contain_exactly(source_project) + expect(projects).to contain_exactly(source_project) end end end @@ -33,7 +33,7 @@ with_them do it 'returns all projects that can be accessed from a given scope' do - expect(all_projects).to contain_exactly(source_project, additional_project) + expect(projects).to contain_exactly(source_project, additional_project) end end end @@ -45,17 +45,15 @@ context 'without scoped projects' do let(:unscoped_project) { build(:project) } - context 'when project is self referential' do - where(:includes_project, :direction, :result) do - ref(:source_project) | :outbound | true - ref(:source_project) | :inbound | true - ref(:unscoped_project) | :outbound | false - ref(:unscoped_project) | :inbound | false - end + where(:includes_project, :direction, :result) do + ref(:source_project) | :outbound | false + ref(:source_project) | :inbound | false + ref(:unscoped_project) | :outbound | false + ref(:unscoped_project) | :inbound | false + end - with_them do - it { is_expected.to be result } - end + with_them do + it { is_expected.to be result } end end @@ -63,8 +61,8 @@ include_context 'with scoped projects' where(:includes_project, :direction, :result) do - ref(:source_project) | :outbound | true - ref(:source_project) | :inbound | true + ref(:source_project) | :outbound | false + ref(:source_project) | :inbound | false ref(:inbound_scoped_project) | :outbound | false ref(:inbound_scoped_project) | :inbound | true ref(:outbound_scoped_project) | :outbound | true diff --git a/spec/models/ci/job_token/outbound_scope_spec.rb b/spec/models/ci/job_token/outbound_scope_spec.rb deleted file mode 100644 index a64b0ca5f92bd2..00000000000000 --- a/spec/models/ci/job_token/outbound_scope_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Ci::JobToken::OutboundScope, feature_category: :continuous_integration do - using RSpec::Parameterized::TableSyntax - - let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } - - let(:scope) { described_class.new(source_project) } - - describe '#all_projects' do - subject(:all_projects) { scope.all_projects } - - context 'when no projects are added to the scope' do - it 'returns the project defining the scope' do - expect(all_projects).to contain_exactly(source_project) - end - end - - context 'when projects are added to the scope' do - include_context 'with scoped projects' - - it 'returns all projects that can be accessed from a given scope' do - expect(subject).to contain_exactly(source_project, outbound_scoped_project) - end - end - end - - describe '#includes?' do - subject { scope.includes?(includes_project) } - - context 'without scoped projects' do - context 'when self referential' do - let(:includes_project) { source_project } - - it { is_expected.to be_truthy } - end - end - - context 'with scoped projects' do - include_context 'with scoped projects' - - where(:includes_project, :result) do - ref(:source_project) | true - ref(:inbound_scoped_project) | false - ref(:outbound_scoped_project) | true - ref(:unscoped_project1) | false - ref(:unscoped_project2) | false - end - - with_them do - it { is_expected.to eq(result) } - end - - context 'when project scope setting is disabled' do - before do - source_project.ci_outbound_job_token_scope_enabled = false - end - - where(:includes_project, :result) do - ref(:source_project) | true - ref(:inbound_scoped_project) | true - ref(:outbound_scoped_project) | true - ref(:unscoped_project1) | true - ref(:unscoped_project2) | true - end - - with_them do - it 'considers any project as part of the scope' do - is_expected.to eq(result) - end - end - end - end - end -end diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index 1d9e76095ee1ea..37c5697350658b 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -25,8 +25,8 @@ end end - describe '#includes?' do - subject { scope.includes?(includes_project) } + describe '#allows?' do + subject { scope.allows?(includes_project) } context 'without scoped projects' do context 'when self referential' do diff --git a/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb b/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb index b2f84ab2869110..50d6e9b9e9590a 100644 --- a/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb @@ -60,7 +60,7 @@ post_graphql_mutation(mutation, current_user: current_user) expect(response).to have_gitlab_http_status(:success) expect(mutation_response.dig('ciJobTokenScope', 'projects', 'nodes')).not_to be_empty - end.to change { Ci::JobToken::Scope.new(project).includes?(target_project) }.from(false).to(true) + end.to change { Ci::JobToken::Scope.new(project).allows?(target_project) }.from(false).to(true) end context 'when invalid target project is provided' do diff --git a/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb b/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb index 2b0adf89f404ba..eb1a87f1cda8c2 100644 --- a/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb @@ -66,7 +66,7 @@ post_graphql_mutation(mutation, current_user: current_user) expect(response).to have_gitlab_http_status(:success) expect(mutation_response.dig('ciJobTokenScope', 'projects', 'nodes')).not_to be_empty - end.to change { Ci::JobToken::Scope.new(project).includes?(target_project) }.from(true).to(false) + end.to change { Ci::JobToken::Scope.new(project).allows?(target_project) }.from(true).to(false) end context 'when invalid target project is provided' do -- GitLab