Commit 2dda6e34 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'sk/339794-update-policy-name-be' into 'master'

Add policy name to ScanExecutionPolicyCommit mutation

See merge request gitlab-org/gitlab!71655
parents 00e6ea07 62e4cb5c
...@@ -4042,6 +4042,7 @@ Input type: `ScanExecutionPolicyCommitInput` ...@@ -4042,6 +4042,7 @@ Input type: `ScanExecutionPolicyCommitInput`
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationscanexecutionpolicycommitname"></a>`name` | [`String`](#string) | Name of the policy. If the name is null, the `name` field from `policy_yaml` is used. |
| <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. | | <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. |
| <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. | | <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. |
| <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. | | <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
......
...@@ -24,6 +24,10 @@ module Mutations ...@@ -24,6 +24,10 @@ module Mutations
required: true, required: true,
description: 'Changes the operation mode.' description: 'Changes the operation mode.'
argument :name, GraphQL::Types::String,
required: false,
description: 'Name of the policy. If the name is null, the `name` field from `policy_yaml` is used.'
field :branch, field :branch,
GraphQL::Types::String, GraphQL::Types::String,
null: true, null: true,
...@@ -32,7 +36,7 @@ module Mutations ...@@ -32,7 +36,7 @@ module Mutations
def resolve(args) def resolve(args)
project = authorized_find!(args[:project_path]) project = authorized_find!(args[:project_path])
result = commit_policy(project, args[:policy_yaml], args[:operation_mode]) result = commit_policy(project, args)
error_message = result[:status] == :error ? result[:message] : nil error_message = result[:status] == :error ? result[:message] : nil
{ {
...@@ -43,9 +47,13 @@ module Mutations ...@@ -43,9 +47,13 @@ module Mutations
private private
def commit_policy(project, policy_yaml, operation_mode) def commit_policy(project, args)
::Security::SecurityOrchestrationPolicies::PolicyCommitService ::Security::SecurityOrchestrationPolicies::PolicyCommitService
.new(project: project, current_user: current_user, params: { policy_yaml: policy_yaml, operation: Types::MutationOperationModeEnum.enum.key(operation_mode).to_sym }) .new(project: project, current_user: current_user, params: {
name: args[:name],
policy_yaml: args[:policy_yaml],
operation: Types::MutationOperationModeEnum.enum.key(args[:operation_mode]).to_sym
})
.execute .execute
end end
end end
......
...@@ -23,7 +23,12 @@ module Security ...@@ -23,7 +23,12 @@ module Security
policy = Gitlab::Config::Loader::Yaml.new(params[:policy_yaml]).load! policy = Gitlab::Config::Loader::Yaml.new(params[:policy_yaml]).load!
updated_policy = ProcessPolicyService.new( updated_policy = ProcessPolicyService.new(
policy_configuration: policy_configuration, policy_configuration: policy_configuration,
params: { operation: params[:operation], policy: policy, type: policy.delete(:type)&.to_sym } params: {
operation: params[:operation],
name: params[:name],
policy: policy,
type: policy.delete(:type)&.to_sym
}
).execute ).execute
YAML.dump(updated_policy.deep_stringify_keys) YAML.dump(updated_policy.deep_stringify_keys)
......
...@@ -11,14 +11,17 @@ module Security ...@@ -11,14 +11,17 @@ module Security
def execute def execute
policy = params[:policy] policy = params[:policy]
type = params[:type] type = params[:type]
name = params[:name]
operation = params[:operation]
raise StandardError, "Invalid policy type" unless Security::OrchestrationPolicyConfiguration::AVAILABLE_POLICY_TYPES.include?(type) raise StandardError, "Invalid policy type" unless Security::OrchestrationPolicyConfiguration::AVAILABLE_POLICY_TYPES.include?(type)
raise StandardError, "Name should be same as the policy name" if name && operation != :replace && policy[:name] != name
policy_hash = policy_configuration.policy_hash.dup || {} policy_hash = policy_configuration.policy_hash.dup || {}
case params[:operation] case operation
when :append then append_to_policy_hash(policy_hash, policy, type) when :append then append_to_policy_hash(policy_hash, policy, type)
when :replace then replace_in_policy_hash(policy_hash, policy, type) when :replace then replace_in_policy_hash(policy_hash, name, policy, type)
when :remove then remove_from_policy_hash(policy_hash, policy, type) when :remove then remove_from_policy_hash(policy_hash, policy, type)
end end
...@@ -35,30 +38,32 @@ module Security ...@@ -35,30 +38,32 @@ module Security
return return
end end
raise StandardError, "Policy already exists with same name" if policy_exists?(policy_hash, policy, type) raise StandardError, "Policy already exists with same name" if policy_exists?(policy_hash, policy[:name], type)
policy_hash[type] += [policy] policy_hash[type] += [policy]
end end
def replace_in_policy_hash(policy_hash, policy, type) def replace_in_policy_hash(policy_hash, name, policy, type)
existing_policy_index = check_if_policy_exists!(policy_hash, policy, type) raise StandardError, "Policy already exists with same name" if name && name != policy[:name] && policy_exists?(policy_hash, policy[:name], type)
existing_policy_index = check_if_policy_exists!(policy_hash, name || policy[:name], type)
policy_hash[type][existing_policy_index] = policy policy_hash[type][existing_policy_index] = policy
end end
def remove_from_policy_hash(policy_hash, policy, type) def remove_from_policy_hash(policy_hash, policy, type)
check_if_policy_exists!(policy_hash, policy, type) check_if_policy_exists!(policy_hash, policy[:name], type)
policy_hash[type].reject! { |p| p[:name] == policy[:name] } policy_hash[type].reject! { |p| p[:name] == policy[:name] }
end end
def check_if_policy_exists!(policy_hash, policy, type) def check_if_policy_exists!(policy_hash, policy_name, type)
existing_policy_index = policy_exists?(policy_hash, policy, type) existing_policy_index = policy_exists?(policy_hash, policy_name, type)
raise StandardError, "Policy does not exist" if existing_policy_index.nil? raise StandardError, "Policy does not exist" if existing_policy_index.nil?
existing_policy_index existing_policy_index
end end
def policy_exists?(policy_hash, policy, type) def policy_exists?(policy_hash, policy_name, type)
policy_hash[type].find_index { |p| p[:name] == policy[:name] } policy_hash[type].find_index { |p| p[:name] == policy_name }
end end
attr_reader :policy_configuration, :params attr_reader :policy_configuration, :params
......
...@@ -10,9 +10,10 @@ RSpec.describe Mutations::SecurityPolicy::CommitScanExecutionPolicy do ...@@ -10,9 +10,10 @@ RSpec.describe Mutations::SecurityPolicy::CommitScanExecutionPolicy do
let_it_be(:policy_management_project) { create(:project, :repository, namespace: user.namespace) } let_it_be(:policy_management_project) { create(:project, :repository, namespace: user.namespace) }
let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration, security_policy_management_project: policy_management_project, project: project) } let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration, security_policy_management_project: policy_management_project, project: project) }
let_it_be(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] } let_it_be(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] }
let_it_be(:policy_yaml) { build(:scan_execution_policy).merge(type: 'scan_execution_policy').to_yaml } let_it_be(:policy_name) { 'Test Policy' }
let_it_be(:policy_yaml) { build(:scan_execution_policy, name: policy_name).merge(type: 'scan_execution_policy').to_yaml }
subject { mutation.resolve(project_path: project.full_path, policy_yaml: policy_yaml, operation_mode: operation_mode) } subject { mutation.resolve(project_path: project.full_path, name: policy_name, policy_yaml: policy_yaml, operation_mode: operation_mode) }
context 'when permission is set for user' do context 'when permission is set for user' do
before do before do
......
...@@ -7,10 +7,11 @@ RSpec.describe 'Create scan execution policy for a project' do ...@@ -7,10 +7,11 @@ RSpec.describe 'Create scan execution policy for a project' do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: current_user.namespace) } let_it_be(:project) { create(:project, :repository, namespace: current_user.namespace) }
let_it_be(:policy_yaml) { build(:scan_execution_policy).merge(type: 'scan_execution_policy').to_yaml } let_it_be(:policy_name) { 'Test Policy' }
let_it_be(:policy_yaml) { build(:scan_execution_policy, name: policy_name).merge(type: 'scan_execution_policy').to_yaml }
def mutation def mutation
variables = { project_path: project.full_path, policy_yaml: policy_yaml, operation_mode: 'APPEND' } variables = { project_path: project.full_path, name: policy_name, policy_yaml: policy_yaml, operation_mode: 'APPEND' }
graphql_mutation(:scan_execution_policy_commit, variables) do graphql_mutation(:scan_execution_policy_commit, variables) do
<<-QL.strip_heredoc <<-QL.strip_heredoc
......
...@@ -3,26 +3,28 @@ ...@@ -3,26 +3,28 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do
include RepoHelpers
describe '#execute' do describe '#execute' do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:current_user) { project.owner } let_it_be(:current_user) { project.owner }
let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration, project: project) } let_it_be(:policy_management_project) { create(:project, :repository, creator: current_user) }
let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration, security_policy_management_project: policy_management_project, project: project) }
let(:policy_hash) { build(:scan_execution_policy, name: 'Test Policy') } let(:policy_hash) { build(:scan_execution_policy, name: 'Test Policy') }
let(:input_policy_yaml) { policy_hash.merge(type: 'scan_execution_policy').to_yaml } let(:input_policy_yaml) { policy_hash.merge(type: 'scan_execution_policy').to_yaml }
let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy_hash])} let(:policy_yaml) { build(:orchestration_policy_yaml, scan_execution_policy: [policy_hash])}
let(:policy_name) { policy_hash[:name] }
let(:operation) { :append } let(:operation) { :append }
let(:params) { { policy_yaml: input_policy_yaml, operation: operation } } let(:params) { { policy_yaml: input_policy_yaml, name: policy_name, operation: operation } }
subject(:service) do subject(:service) do
described_class.new(project: project, current_user: current_user, params: params) described_class.new(project: project, current_user: current_user, params: params)
end end
before do around do |example|
allow_next_instance_of(Repository) do |repository| Timecop.scale(60) { example.run }
allow(repository).to receive(:blob_data_at).and_return(policy_yaml)
end
end end
context 'when policy_yaml is invalid' do context 'when policy_yaml is invalid' do
...@@ -56,11 +58,16 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do ...@@ -56,11 +58,16 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do
context 'when policy already exists in policy project' do context 'when policy already exists in policy project' do
before do before do
allow_next_instance_of(::Files::UpdateService) do |instance| create_file_in_repo(
allow(instance).to receive(:execute).and_return({ status: :success }) policy_management_project,
end policy_management_project.default_branch_or_main,
policy_management_project.default_branch_or_main,
Security::OrchestrationPolicyConfiguration::POLICY_PATH,
policy_yaml
)
policy_configuration.security_policy_management_project.add_developer(current_user) policy_configuration.security_policy_management_project.add_developer(current_user)
policy_configuration.clear_memoization(:policy_hash)
policy_configuration.clear_memoization(:policy_blob)
end end
context 'append' do context 'append' do
...@@ -74,23 +81,33 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do ...@@ -74,23 +81,33 @@ RSpec.describe Security::SecurityOrchestrationPolicies::PolicyCommitService do
context 'replace' do context 'replace' do
let(:operation) { :replace } let(:operation) { :replace }
let(:input_policy_yaml) { build(:scan_execution_policy, name: 'Updated Policy').merge(type: 'scan_execution_policy').to_yaml }
let(:policy_name) { 'Test Policy' }
it 'creates branch' do it 'creates branch with updated policy' do
response = service.execute response = service.execute
expect(response[:status]).to eq(:success) expect(response[:status]).to eq(:success)
expect(response[:branch]).not_to be_nil expect(response[:branch]).not_to be_nil
updated_policy_blob = policy_management_project.repository.blob_data_at(response[:branch], Security::OrchestrationPolicyConfiguration::POLICY_PATH)
updated_policy_yaml = Gitlab::Config::Loader::Yaml.new(updated_policy_blob).load!
expect(updated_policy_yaml[:scan_execution_policy][0][:name]).to eq('Updated Policy')
end end
end end
context 'remove' do context 'remove' do
let(:operation) { :remove } let(:operation) { :remove }
it 'creates branch' do it 'creates branch with removed policy' do
response = service.execute response = service.execute
expect(response[:status]).to eq(:success) expect(response[:status]).to eq(:success)
expect(response[:branch]).not_to be_nil expect(response[:branch]).not_to be_nil
updated_policy_blob = policy_management_project.repository.blob_data_at(response[:branch], Security::OrchestrationPolicyConfiguration::POLICY_PATH)
updated_policy_yaml = Gitlab::Config::Loader::Yaml.new(updated_policy_blob).load!
expect(updated_policy_yaml[:scan_execution_policy]).to be_empty
end end
end end
end end
......
...@@ -11,6 +11,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do ...@@ -11,6 +11,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do
let(:policy_yaml) { Gitlab::Config::Loader::Yaml.new(policy.to_yaml).load! } let(:policy_yaml) { Gitlab::Config::Loader::Yaml.new(policy.to_yaml).load! }
let(:type) { :scan_execution_policy } let(:type) { :scan_execution_policy }
let(:operation) { :append } let(:operation) { :append }
let(:policy_name) { policy[:name] }
let(:repository_with_existing_policy_yaml) do let(:repository_with_existing_policy_yaml) do
pipeline_policy = build(:scan_execution_policy, name: 'Test Policy') pipeline_policy = build(:scan_execution_policy, name: 'Test Policy')
...@@ -22,18 +23,27 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do ...@@ -22,18 +23,27 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do
build(:orchestration_policy_yaml, scan_execution_policy: [pipeline_policy, scheduled_policy]) build(:orchestration_policy_yaml, scan_execution_policy: [pipeline_policy, scheduled_policy])
end end
subject(:service) { described_class.new(policy_configuration: policy_configuration, params: { policy: policy_yaml, operation: operation, type: type }) } subject(:service) { described_class.new(policy_configuration: policy_configuration, params: { policy: policy_yaml, name: policy_name, operation: operation, type: type }) }
context 'when policy is invalid' do context 'when policy is invalid' do
let(:policy) { { invalid_name: 'invalid' } } let(:policy_name) { 'invalid' }
let(:policy) { { name: 'invalid', invalid_field: 'invalid' } }
it 'raises StandardError' do it 'raises StandardError' do
expect { service.execute }.to raise_error(StandardError, 'Invalid policy yaml') expect { service.execute }.to raise_error(StandardError, 'Invalid policy yaml')
end end
end end
context 'when policy name is not same as in policy' do
let(:policy_name) { 'invalid' }
it 'raises StandardError' do
expect { service.execute }.to raise_error(StandardError, 'Name should be same as the policy name')
end
end
context 'when type is invalid' do context 'when type is invalid' do
let(:type) { :invalid_type} let(:type) { :invalid_type }
it 'raises StandardError' do it 'raises StandardError' do
expect { service.execute }.to raise_error(StandardError, 'Invalid policy type') expect { service.execute }.to raise_error(StandardError, 'Invalid policy type')
...@@ -79,6 +89,10 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do ...@@ -79,6 +89,10 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do
context 'replace policy' do context 'replace policy' do
let(:operation) { :replace } let(:operation) { :replace }
before do
allow(policy_configuration).to receive(:policy_hash).and_return(Gitlab::Config::Loader::Yaml.new(repository_with_existing_policy_yaml).load!)
end
context 'when policy is not present in repository' do context 'when policy is not present in repository' do
before do before do
allow(policy_configuration).to receive(:policy_hash).and_return(Gitlab::Config::Loader::Yaml.new(repository_policy_yaml).load!) allow(policy_configuration).to receive(:policy_hash).and_return(Gitlab::Config::Loader::Yaml.new(repository_policy_yaml).load!)
...@@ -89,17 +103,45 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do ...@@ -89,17 +103,45 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessPolicyService do
end end
end end
context 'when policy with same name already exists in repository' do context 'when policy name is empty' do
before do let(:policy_name) { nil }
allow(policy_configuration).to receive(:policy_hash).and_return(Gitlab::Config::Loader::Yaml.new(repository_with_existing_policy_yaml).load!)
it 'does not modify the policy name' do
result = service.execute
expect(result[:scan_execution_policy].first).to eq(policy_yaml)
end end
end
context 'when policy with same name already exists in repository' do
it 'replaces the policy' do it 'replaces the policy' do
result = service.execute result = service.execute
expect(result[:scan_execution_policy].first[:enabled]).to be_falsey expect(result[:scan_execution_policy].first[:enabled]).to be_falsey
end end
end end
context 'when policy name is not same as in policy' do
let(:policy_yaml) do
Gitlab::Config::Loader::Yaml.new(build(:scan_execution_policy, name: 'Updated Policy', enabled: false).to_yaml).load!
end
it 'updates the policy name' do
result = service.execute
expect(result[:scan_execution_policy].first[:name]).to eq('Updated Policy')
end
end
context 'when name of the policy to be updated already exists' do
let(:policy_yaml) do
Gitlab::Config::Loader::Yaml.new(build(:scan_execution_policy, name: 'Scheduled DAST', enabled: false).to_yaml).load!
end
it 'raises StandardError' do
expect { service.execute }.to raise_error(StandardError, 'Policy already exists with same name')
end
end
end end
context 'remove policy' do context 'remove policy' do
......
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