diff --git a/app/graphql/mutations/ci/job_token_scope/remove_project.rb b/app/graphql/mutations/ci/job_token_scope/remove_project.rb
index f503b4f2f7aab2d5f8898a15ce21f0e0c517f8ff..e5f40bbf87e67f7c1daf262042c52d6def5da6db 100644
--- a/app/graphql/mutations/ci/job_token_scope/remove_project.rb
+++ b/app/graphql/mutations/ci/job_token_scope/remove_project.rb
@@ -18,18 +18,23 @@ class RemoveProject < BaseMutation
required: true,
description: 'Project to be removed from the CI job token scope.'
+ argument :direction,
+ ::Types::Ci::JobTokenScope::DirectionEnum,
+ required: false,
+ description: 'Direction of access, which defaults to outbound.'
+
field :ci_job_token_scope,
Types::Ci::JobTokenScopeType,
null: true,
description: "CI job token's scope of access."
- def resolve(project_path:, target_project_path:)
+ def resolve(project_path:, target_project_path:, direction: :outbound)
project = authorized_find!(project_path)
target_project = Project.find_by_full_path(target_project_path)
result = ::Ci::JobTokenScope::RemoveProjectService
.new(project, current_user)
- .execute(target_project)
+ .execute(target_project, direction: direction)
if result.success?
{
diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb
index 4fb577f58bc6d6dcc23032a2589f17c03ddcd7a3..774d85e3d3c0d766091e952306f9add0aaa7473b 100644
--- a/app/models/ci/job_token/project_scope_link.rb
+++ b/app/models/ci/job_token/project_scope_link.rb
@@ -13,8 +13,9 @@ class ProjectScopeLink < Ci::ApplicationRecord
belongs_to :target_project, class_name: 'Project'
belongs_to :added_by, class_name: 'User'
- scope :with_source, ->(project) { where(source_project: project) }
- scope :with_target, ->(project) { where(target_project: project) }
+ scope :with_access_direction, ->(direction) { where(direction: direction) }
+ 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/app/services/ci/job_token_scope/remove_project_service.rb b/app/services/ci/job_token_scope/remove_project_service.rb
index 15644e529d99878b7411a4f775c74a96e2081d09..d21eff2b6193eb29af5f4e5ac89eaade25a4548d 100644
--- a/app/services/ci/job_token_scope/remove_project_service.rb
+++ b/app/services/ci/job_token_scope/remove_project_service.rb
@@ -5,14 +5,16 @@ module JobTokenScope
class RemoveProjectService < ::BaseService
include EditScopeValidations
- def execute(target_project)
+ def execute(target_project, direction: :outbound)
validate_edit!(project, target_project, current_user)
if project == target_project
return ServiceResponse.error(message: "Source project cannot be removed from the job token scope")
end
- link = ::Ci::JobToken::ProjectScopeLink.for_source_and_target(project, target_project)
+ link = ::Ci::JobToken::ProjectScopeLink
+ .with_access_direction(direction)
+ .for_source_and_target(project, target_project)
unless link
return ServiceResponse.error(message: "Target project is not in the job token scope")
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d9e07d45355c326f1b2ee7bd06e74893b2ac95da..3e1c91040c9f3c435e1895e3a171c2e9cb730850 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1204,6 +1204,7 @@ Input type: `CiJobTokenScopeRemoveProjectInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `direction` | [`CiJobTokenScopeDirection`](#cijobtokenscopedirection) | Direction of access, which defaults to outbound. |
| `projectPath` | [`ID!`](#id) | Project that the CI job token scope belongs to. |
| `targetProjectPath` | [`ID!`](#id) | Project to be removed from the CI job token scope. |
diff --git a/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb b/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb
index 4a0501bddbddb47989859251f348ca89a91c1199..7fb45e934744918091edf760af1fa431db6aae43 100644
--- a/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb
+++ b/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb
@@ -17,6 +17,7 @@
end
let(:target_project_path) { target_project.full_path }
+ let(:links_relation) { Ci::JobToken::ProjectScopeLink.with_source(project).with_target(target_project) }
subject do
mutation.resolve(project_path: project.full_path, target_project_path: target_project_path)
@@ -45,18 +46,39 @@
target_project.add_guest(current_user)
end
- it 'removes target project from the job token scope' do
- expect do
- expect(subject).to include(ci_job_token_scope: be_present, errors: be_empty)
- end.to change { Ci::JobToken::ProjectScopeLink.count }.by(-1)
+ context 'with no direction specified' do
+ it 'defaults to removing an outbound link to the target project' do
+ expect do
+ expect(subject).to include(ci_job_token_scope: be_present, errors: be_empty)
+ end.to change { Ci::JobToken::ProjectScopeLink.count }.by(-1)
+
+ expect(links_relation.outbound.reload).to be_empty
+ end
+ end
+
+ context 'with direction specified' do
+ let(:service) { instance_double('Ci::JobTokenScope::RemoveProjectService') }
+
+ subject do
+ mutation.resolve(project_path: project.full_path, target_project_path: target_project_path, direction: 'inbound')
+ end
+
+ it 'executes project removal for the correct direction' do
+ expect(::Ci::JobTokenScope::RemoveProjectService)
+ .to receive(:new).with(project, current_user).and_return(service)
+ expect(service).to receive(:execute).with(target_project, direction: 'inbound')
+ .and_return(instance_double('ServiceResponse', "success?": true))
+
+ subject
+ end
end
context 'when the service returns an error' do
- let(:service) { double(:service) }
+ let(:service) { instance_double('Ci::JobTokenScope::RemoveProjectService') }
it 'returns an error response' do
expect(::Ci::JobTokenScope::RemoveProjectService).to receive(:new).with(project, current_user).and_return(service)
- expect(service).to receive(:execute).with(target_project).and_return(ServiceResponse.error(message: 'The error message'))
+ expect(service).to receive(:execute).with(target_project, direction: :outbound).and_return(ServiceResponse.error(message: 'The error message'))
expect(subject.fetch(:ci_job_token_scope)).to be_nil
expect(subject.fetch(:errors)).to include("The error message")
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 61d5c56ae8a2c310f39ee8a7c65721ed95ef9026..805f300072d52af32fb06430baadecfcc6db9531 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
@@ -22,6 +22,7 @@
let(:variables) do
{
+ direction: 'OUTBOUND',
project_path: project.full_path,
target_project_path: target_project.full_path
}
@@ -67,7 +68,7 @@
target_project.add_guest(current_user)
end
- it 'removes the target project from the job token scope' do
+ it 'removes the target project from the job token outbound scope' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)