Commit 08e8188a authored by Fabio Pitino - OOO until July 6's avatar Fabio Pitino - OOO until July 6 Committed by Alex Kalderimis

Add CIJobToken project allow list resolver

Changelog: added
parent b8c70280
# frozen_string_literal: true
module Mutations
module Ci
module JobTokenScope
class AddProject < BaseMutation
include FindsProject
graphql_name 'CiJobTokenScopeAddProject'
authorize :admin_project
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project that the CI job token scope belongs to.'
argument :target_project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project to be added to the CI job token scope.'
field :ci_job_token_scope,
Types::Ci::JobTokenScopeType,
null: true,
description: "The CI job token's scope of access."
def resolve(project_path:, target_project_path:)
project = authorized_find!(project_path)
target_project = Project.find_by_full_path(target_project_path)
result = ::Ci::JobTokenScope::AddProjectService
.new(project, current_user)
.execute(target_project)
if result.success?
{
ci_job_token_scope: ::Ci::JobToken::Scope.new(project),
errors: []
}
else
{
ci_job_token_scope: nil,
errors: [result.message]
}
end
end
end
end
end
end
......@@ -99,6 +99,7 @@ module Types
mount_mutation Mutations::Ci::CiCdSettingsUpdate
mount_mutation Mutations::Ci::Job::Play
mount_mutation Mutations::Ci::Job::Retry
mount_mutation Mutations::Ci::JobTokenScope::AddProject
mount_mutation Mutations::Ci::Runner::Update, feature_flag: :runner_graphql_query
mount_mutation Mutations::Ci::Runner::Delete, feature_flag: :runner_graphql_query
mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, feature_flag: :runner_graphql_query
......
# frozen_string_literal: true
module Ci
module JobTokenScope
class AddProjectService < ::BaseService
TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND = "The target_project that you are attempting to access does " \
"not exist or you don't have permission to perform this action"
def execute(target_project)
if error_response = validation_error(target_project)
return error_response
end
link = add_project!(target_project)
ServiceResponse.success(payload: { project_link: link })
rescue ActiveRecord::RecordNotUnique
ServiceResponse.error(message: "Target project is already in the job token scope")
rescue ActiveRecord::RecordInvalid => e
ServiceResponse.error(message: e.message)
end
private
def validation_error(target_project)
unless project.ci_job_token_scope_enabled?
return ServiceResponse.error(message: "Job token scope is disabled for this project")
end
unless can?(current_user, :admin_project, project)
return ServiceResponse.error(message: "Insufficient permissions to modify the job token scope")
end
unless target_project
return ServiceResponse.error(message: TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND)
end
unless can?(current_user, :read_project, target_project)
return ServiceResponse.error(message: TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND)
end
nil
end
def add_project!(target_project)
::Ci::JobToken::ProjectScopeLink.create!(
source_project: project,
target_project: target_project,
added_by: current_user
)
end
end
end
end
......@@ -780,6 +780,26 @@ Input type: `CiCdSettingsUpdateInput`
| <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcicdsettingsupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.ciJobTokenScopeAddProject`
Input type: `CiJobTokenScopeAddProjectInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcijobtokenscopeaddprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcijobtokenscopeaddprojectprojectpath"></a>`projectPath` | [`ID!`](#id) | The project that the CI job token scope belongs to. |
| <a id="mutationcijobtokenscopeaddprojecttargetprojectpath"></a>`targetProjectPath` | [`ID!`](#id) | The project to be added to the CI job token scope. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcijobtokenscopeaddprojectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | The CI job token's scope of access. |
| <a id="mutationcijobtokenscopeaddprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcijobtokenscopeaddprojecterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.clusterAgentDelete`
Input type: `ClusterAgentDeleteInput`
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Ci::JobTokenScope::AddProject do
let(:mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil)
end
describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:target_project) { create(:project) }
let(:target_project_path) { target_project.full_path }
subject do
mutation.resolve(project_path: project.full_path, target_project_path: target_project_path)
end
context 'when user is not logged in' do
let(:current_user) { nil }
it 'raises error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user is logged in' do
let(:current_user) { create(:user) }
context 'when user does not have permissions to admin project' do
it 'raises error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user has permissions to admin project and read target project' do
before do
project.add_maintainer(current_user)
target_project.add_guest(current_user)
end
it 'adds target project to 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)
end
context 'when the service returns an error' do
let(:service) { double(:service) }
it 'returns an error response' do
expect(::Ci::JobTokenScope::AddProjectService).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(subject.fetch(:ci_job_token_scope)).to be_nil
expect(subject.fetch(:errors)).to include("The error message")
end
end
end
end
end
end
......@@ -29,7 +29,7 @@ RSpec.describe Ci::JobToken::Scope do
end
end
describe 'includes?' do
describe '#includes?' do
subject { scope.includes?(target_project) }
context 'when param is the project defining the scope' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'CiJobTokenScopeAddProject' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:target_project) { create(:project) }
let(:variables) do
{
project_path: project.full_path,
target_project_path: target_project.full_path
}
end
let(:mutation) do
graphql_mutation(:ci_job_token_scope_add_project, variables) do
<<~QL
errors
ciJobTokenScope {
projects {
nodes {
path
}
}
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:ci_job_token_scope_add_project) }
context 'when unauthorized' do
let(:current_user) { create(:user) }
context 'when not a maintainer' do
before do
project.add_developer(current_user)
end
it 'has graphql errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect(graphql_errors).not_to be_empty
end
end
end
context 'when authorized' do
let_it_be(:current_user) { project.owner }
before do
target_project.add_developer(current_user)
end
it 'adds the target project to the job token scope' do
expect do
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
context 'when invalid target project is provided' do
before do
variables[:target_project_path] = 'unknown/project'
end
it 'has mutation errors' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to contain_exactly(Ci::JobTokenScope::AddProjectService::TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::JobTokenScope::AddProjectService do
let(:service) { described_class.new(project, current_user) }
let_it_be(:project) { create(:project) }
let_it_be(:target_project) { create(:project) }
let_it_be(:current_user) { create(:user) }
describe '#execute' do
subject(:result) { service.execute(target_project) }
shared_examples 'returns error' do |error|
it 'returns an error response', :aggregate_failures do
expect(result).to be_error
expect(result.message).to eq(error)
end
end
context 'when job token scope is disabled for the given project' do
before do
allow(project).to receive(:ci_job_token_scope_enabled?).and_return(false)
end
it_behaves_like 'returns error', 'Job token scope is disabled for this project'
end
context 'when user does not have permissions to edit the job token scope' do
it_behaves_like 'returns error', 'Insufficient permissions to modify the job token scope'
end
context 'when user has permissions to edit the job token scope' do
before do
project.add_maintainer(current_user)
end
context 'when target project is not provided' do
let(:target_project) { nil }
it_behaves_like 'returns error', Ci::JobTokenScope::AddProjectService::TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND
end
context 'when target project is provided' do
context 'when user does not have permissions to read the target project' do
it_behaves_like 'returns error', Ci::JobTokenScope::AddProjectService::TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND
end
context 'when user has permissions to read the target project' do
before do
target_project.add_guest(current_user)
end
it 'adds the project to the scope' do
expect do
expect(result).to be_success
end.to change { Ci::JobToken::ProjectScopeLink.count }.by(1)
end
context 'when target project is already in scope' do
before do
create(:ci_job_token_project_scope_link,
source_project: project,
target_project: target_project)
end
it_behaves_like 'returns error', "Target project is already in the job token scope"
end
end
context 'when target project is same as the source project' do
let(:target_project) { project }
it_behaves_like 'returns error', "Validation failed: Target project can't be the same as the source project"
end
end
end
end
end
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