Commit 9cbae32c authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch...

Merge branch '347069-update-graphql-mutation-to-assign-security-policy-project-to-group' into 'master'

Update mutation to create project and assign security policy to a group

See merge request gitlab-org/gitlab!83191
parents 134094a4 039cc718
......@@ -26,4 +26,8 @@ class BaseContainerService
def group_container?
container.is_a?(::Group)
end
def namespace_container?
container.is_a?(::Namespace)
end
end
......@@ -4336,7 +4336,7 @@ Input type: `ScanExecutionPolicyCommitInput`
### `Mutation.securityPolicyProjectAssign`
Assigns the specified project(`security_policy_project_id`) as security policy project for the given project(`project_path`). If the project already has a security policy project, this reassigns the project's security policy project with the given `security_policy_project_id`.
Assigns the specified project(`security_policy_project_id`) as security policy project for the given project(`full_path`). If the project already has a security policy project, this reassigns the project's security policy project with the given `security_policy_project_id`.
Input type: `SecurityPolicyProjectAssignInput`
......@@ -4345,7 +4345,8 @@ Input type: `SecurityPolicyProjectAssignInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationsecuritypolicyprojectassignclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationsecuritypolicyprojectassignprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
| <a id="mutationsecuritypolicyprojectassignfullpath"></a>`fullPath` | [`String`](#string) | Full path of the project. |
| <a id="mutationsecuritypolicyprojectassignprojectpath"></a>`projectPath` **{warning-solid}** | [`ID`](#id) | **Deprecated:** Use `fullPath`. Deprecated in 14.10. |
| <a id="mutationsecuritypolicyprojectassignsecuritypolicyprojectid"></a>`securityPolicyProjectId` | [`ProjectID!`](#projectid) | ID of the security policy project. |
#### Fields
......@@ -4357,7 +4358,7 @@ Input type: `SecurityPolicyProjectAssignInput`
### `Mutation.securityPolicyProjectCreate`
Creates and assigns a security policy project for the given project(`project_path`).
Creates and assigns a security policy project for the given project (`full_path`).
Input type: `SecurityPolicyProjectCreateInput`
......@@ -4366,7 +4367,8 @@ Input type: `SecurityPolicyProjectCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationsecuritypolicyprojectcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationsecuritypolicyprojectcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
| <a id="mutationsecuritypolicyprojectcreatefullpath"></a>`fullPath` | [`String`](#string) | Full path of the project. |
| <a id="mutationsecuritypolicyprojectcreateprojectpath"></a>`projectPath` **{warning-solid}** | [`ID`](#id) | **Deprecated:** Use `fullPath`. Deprecated in 14.10. |
#### Fields
......@@ -4,7 +4,7 @@ module Mutations
module FindsProjectOrGroupForSecurityPolicies
private
def find_object(args)
def find_object(**args)
full_path = args[:project_path].presence || args[:full_path]
if full_path.blank?
......
......@@ -5,15 +5,20 @@ module Mutations
class AssignSecurityPolicyProject < BaseMutation
graphql_name 'SecurityPolicyProjectAssign'
description 'Assigns the specified project(`security_policy_project_id`) as security policy project '\
'for the given project(`project_path`). If the project already has a security policy project, '\
'for the given project(`full_path`). If the project already has a security policy project, '\
'this reassigns the project\'s security policy project with the given `security_policy_project_id`'
include FindsProject
include FindsProjectOrGroupForSecurityPolicies
authorize :update_security_orchestration_policy_project
argument :full_path, GraphQL::Types::String,
required: false,
description: 'Full path of the project.'
argument :project_path, GraphQL::Types::ID,
required: true,
required: false,
deprecated: { reason: 'Use `fullPath`', milestone: '14.10' },
description: 'Full path of the project.'
argument :security_policy_project_id, ::Types::GlobalIDType[::Project],
......@@ -21,12 +26,12 @@ module Mutations
description: 'ID of the security policy project.'
def resolve(args)
project = authorized_find!(args[:project_path])
project_or_group = authorized_find!(**args)
policy_project = find_policy_project(args[:security_policy_project_id])
raise_resource_not_available_error! unless policy_project.present?
result = assign_project(project, policy_project)
result = assign_project(project_or_group, policy_project)
{
errors: result.success? ? [] : [result.message]
}
......@@ -41,9 +46,9 @@ module Mutations
::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(id, expected_type: Project))
end
def assign_project(project, policy_project)
def assign_project(project_or_group, policy_project)
::Security::Orchestration::AssignService
.new(project, current_user, policy_project_id: policy_project.id)
.new(container: project_or_group, current_user: current_user, params: { policy_project_id: policy_project.id })
.execute
end
end
......
......@@ -4,14 +4,19 @@ module Mutations
module SecurityPolicy
class CreateSecurityPolicyProject < BaseMutation
graphql_name 'SecurityPolicyProjectCreate'
description 'Creates and assigns a security policy project for the given project(`project_path`)'
description 'Creates and assigns a security policy project for the given project (`full_path`)'
include FindsProject
include FindsProjectOrGroupForSecurityPolicies
authorize :update_security_orchestration_policy_project
argument :full_path, GraphQL::Types::String,
required: false,
description: 'Full path of the project.'
argument :project_path, GraphQL::Types::ID,
required: true,
required: false,
deprecated: { reason: 'Use `fullPath`', milestone: '14.10' },
description: 'Full path of the project.'
field :project, Types::ProjectType,
......@@ -19,9 +24,9 @@ module Mutations
description: 'Security Policy Project that was created.'
def resolve(args)
project = authorized_find!(args[:project_path])
project_or_group = authorized_find!(**args)
result = create_project(project)
result = create_project(project_or_group)
return { project: nil, errors: [result[:message]] } if result[:status] == :error
{
......@@ -32,9 +37,9 @@ module Mutations
private
def create_project(project)
def create_project(project_or_group)
::Security::SecurityOrchestrationPolicies::ProjectCreateService
.new(project: project, current_user: current_user)
.new(container: project_or_group, current_user: current_user)
.execute
end
end
......
......@@ -5,13 +5,14 @@ module EE
module CreateService
extend ::Gitlab::Utils::Override
attr_reader :security_policy_target_project_id
attr_reader :security_policy_target_project_id, :security_policy_target_namespace_id
override :initialize
def initialize(user, params)
super
@security_policy_target_project_id = @params.delete(:security_policy_target_project_id)
@security_policy_target_namespace_id = @params.delete(:security_policy_target_namespace_id)
end
override :execute
......@@ -66,12 +67,19 @@ module EE
end
def create_security_policy_configuration_if_exists
return unless security_policy_target_project_id.present?
security_policy_target = find_security_policy_target
return if security_policy_target.blank?
if (security_policy_target_project = ::Project.find(security_policy_target_project_id))
::Security::Orchestration::AssignService
.new(security_policy_target_project, current_user, policy_project_id: project.id)
::Security::Orchestration::AssignService
.new(container: security_policy_target, current_user: current_user, params: { policy_project_id: project.id })
.execute
end
def find_security_policy_target
if security_policy_target_project_id.present?
::Project.find_by_id(security_policy_target_project_id)
elsif security_policy_target_namespace_id.present?
::Namespace.find_by_id(security_policy_target_namespace_id)
end
end
......
......@@ -2,7 +2,7 @@
module Security
module Orchestration
class AssignService < ::BaseService
class AssignService < ::BaseContainerService
def execute
res = create_or_update_security_policy_configuration
......@@ -11,7 +11,7 @@ module Security
rescue ActiveRecord::RecordNotFound => _
error(_('Policy project doesn\'t exist'))
rescue ActiveRecord::RecordInvalid => _
error(_('Couldn\'t assign policy to project'))
error(_('Couldn\'t assign policy to project or group'))
end
private
......@@ -24,18 +24,18 @@ module Security
policy_project = Project.find(policy_project_id)
if has_existing_policy?
project.security_orchestration_policy_configuration.update!(
container.security_orchestration_policy_configuration.update!(
security_policy_management_project_id: policy_project.id
)
else
project.create_security_orchestration_policy_configuration! do |p|
container.create_security_orchestration_policy_configuration! do |p|
p.security_policy_management_project_id = policy_project.id
end
end
end
def unassign_policy_project
project.security_orchestration_policy_configuration.delete
container.security_orchestration_policy_configuration.delete
end
def success
......@@ -47,7 +47,7 @@ module Security
end
def has_existing_policy?
project.security_orchestration_policy_configuration.present?
container.security_orchestration_policy_configuration.present?
end
def policy_project_id
......
......@@ -2,21 +2,23 @@
module Security
module SecurityOrchestrationPolicies
class ProjectCreateService < ::BaseProjectService
class ProjectCreateService < ::BaseContainerService
ACCESS_LEVELS_TO_ADD = [Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER].freeze
README_TEMPLATE_PATH = Rails.root.join('ee', 'app', 'views', 'projects', 'security', 'policies', 'readme.md.tt')
def execute
return error('Security Policy project already exists.') if project.security_orchestration_policy_configuration.present?
return error('Security Policy project already exists.') if container.security_orchestration_policy_configuration.present?
policy_project = ::Projects::CreateService.new(current_user, create_project_params).execute
return error(policy_project.errors.full_messages.join(',')) unless policy_project.saved?
members = add_members(policy_project)
errors = members.flat_map { |member| member.errors.full_messages }
if project_container?
members = add_members(policy_project)
errors = members.flat_map { |member| member.errors.full_messages }
return error('Project was created and assigned as security policy project, but failed adding users to the project.') if errors.any?
return error('Project was created and assigned as security policy project, but failed adding users to the project.') if errors.any?
end
success(policy_project: policy_project)
end
......@@ -24,18 +26,21 @@ module Security
private
def add_members(policy_project)
developers_and_maintainers = project.team.members_with_access_levels(ACCESS_LEVELS_TO_ADD)
members_to_add = developers_and_maintainers - policy_project.team.members
policy_project.add_users(members_to_add, :developer)
end
def developers_and_maintainers
container.team.members_with_access_levels(ACCESS_LEVELS_TO_ADD)
end
def create_project_params
{
visibility_level: project.visibility_level,
security_policy_target_project_id: project.id,
name: "#{project.name} - Security policy project",
creator: current_user,
visibility_level: container.visibility_level,
name: "#{container.name} - Security policy project",
description: "This project is automatically generated to manage security policies for the project.",
namespace_id: project.namespace_id,
namespace_id: namespace_id,
initialize_with_readme: true,
container_registry_enabled: false,
packages_enabled: false,
......@@ -44,14 +49,28 @@ module Security
wiki_enabled: false,
snippets_enabled: false,
readme_template: readme_template
}
}.merge(security_policy_target_id)
end
def security_policy_target_id
if project_container?
{ security_policy_target_project_id: container.id }
elsif namespace_container?
{ security_policy_target_namespace_id: container.id }
end
end
def namespace_id
if project_container?
container.namespace_id
elsif namespace_container?
container.id
end
end
def readme_template
ERB.new(File.read(README_TEMPLATE_PATH), trim_mode: '<>').result(binding)
end
attr_reader :project
end
end
end
# Security Policy Project for <%= @project.name %>
# Security Policy Project for <%= @container.name %>
This project is automatically generated to manage security policies for the project.
......@@ -35,7 +35,7 @@ You can read more about the format and policies schema in the [documentation](ht
## Default branch protection settings
This project is preconfigured with the default branch set as a protected branch, and only [project](<%= @project.web_url %>)
maintainers/owners have permission to merge into that branch. This overrides any default branch protection both at the
This project is preconfigured with the default branch set as a protected branch, and only maintainers/owners of
[<%= @container.name %>](<%= @container.web_url %>) have permission to merge into that branch. This overrides any default branch protection both at the
[group level](https://docs.gitlab.com/ee/user/group/index.html#change-the-default-branch-protection-of-a-group) and at the
[instance level](https://docs.gitlab.com/ee/user/admin_area/settings/visibility_and_access_controls.html#default-branch-protection).
......@@ -7,35 +7,60 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
describe '#resolve' do
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:namespace) { create(:group) }
let_it_be(:project) { create(:project, namespace: owner.namespace) }
let_it_be(:policy_project) { create(:project) }
let_it_be(:policy_project_id) { GitlabSchema.id_from_object(policy_project) }
let(:current_user) { owner }
subject { mutation.resolve(project_path: project.full_path, security_policy_project_id: policy_project_id) }
subject { mutation.resolve(full_path: container.full_path, security_policy_project_id: policy_project_id) }
context 'when permission is set for user' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
shared_context 'assigns security policy project' do
context 'when licensed feature is available' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
context 'when user is an owner of the project' do
it 'assigns the security policy project' do
result = subject
context 'when user is an owner of the container' do
before do
container.add_owner(owner)
end
expect(result[:errors]).to be_empty
expect(project.security_orchestration_policy_configuration).not_to be_nil
expect(project.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
it 'assigns the security policy project' do
result = subject
expect(result[:errors]).to be_empty
expect(container.security_orchestration_policy_configuration).not_to be_nil
expect(container.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
end
end
context 'when user is not an owner' do
let(:current_user) { maintainer }
before do
container.add_maintainer(maintainer)
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
context 'when user is not an owner' do
let(:current_user) { user }
context 'when policy_project_id is invalid' do
let_it_be(:policy_project_id) { 'invalid' }
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when feature is not licensed' do
before do
project.add_maintainer(user)
stub_licensed_features(security_orchestration_policies: false)
end
it 'raises exception' do
......@@ -44,21 +69,44 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
end
end
context 'when policy_project_id is invalid' do
let_it_be(:policy_project_id) { 'invalid' }
context 'when both fullPath and projectPath are not provided' do
subject { mutation.resolve(security_policy_project_id: policy_project_id) }
before do
stub_licensed_features(security_orchestration_policies: true)
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
end
context 'when feature is not licensed' do
before do
stub_licensed_features(security_orchestration_policies: false)
context 'for project' do
let(:container) { project }
it_behaves_like 'assigns security policy project'
end
context 'for namespace' do
let(:container) { namespace }
context 'when feature is enabled' do
before do
stub_feature_flags(group_level_security_policies: namespace)
end
it_behaves_like 'assigns security policy project'
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
context 'when feature is disabled' do
before do
stub_licensed_features(security_orchestration_policies: true)
stub_feature_flags(group_level_security_policies: false)
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
......
......@@ -6,34 +6,51 @@ RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do
describe '#resolve' do
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:namespace) { create(:group) }
let_it_be(:project) { create(:project, namespace: owner.namespace) }
let(:current_user) { owner }
subject { mutation.resolve(project_path: project.full_path) }
subject { mutation.resolve(full_path: container.full_path) }
context 'when permission is set for user' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
shared_context 'creates security policy project' do
context 'when licensed feature is available' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
context 'when user is an owner of the container' do
let(:current_user) { owner }
context 'when user is an owner of the project' do
let(:current_user) { owner }
before do
namespace.add_owner(owner)
end
it 'returns project' do
result = subject
it 'returns project' do
result = subject
expect(result[:errors]).to be_empty
expect(result[:project]).to eq(Project.last)
expect(result[:errors]).to be_empty
expect(result[:project]).to eq(Project.last)
end
end
end
context 'when user is not an owner' do
let(:current_user) { user }
context 'when user is not an owner' do
let(:current_user) { maintainer }
before do
container.add_maintainer(maintainer)
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
context 'when feature is not licensed' do
before do
project.add_maintainer(user)
stub_licensed_features(security_orchestration_policies: false)
end
it 'raises exception' do
......@@ -42,13 +59,44 @@ RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do
end
end
context 'when feature is not licensed' do
context 'when both fullPath and projectPath are not provided' do
subject { mutation.resolve({}) }
before do
stub_licensed_features(security_orchestration_policies: false)
stub_licensed_features(security_orchestration_policies: true)
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
end
context 'for project' do
let(:container) { project }
it_behaves_like 'creates security policy project'
end
context 'for namespace' do
let(:container) { namespace }
context 'when feature is enabled' do
before do
stub_feature_flags(group_level_security_policies: namespace)
end
it_behaves_like 'creates security policy project'
end
context 'when feature is disabled' do
before do
stub_licensed_features(security_orchestration_policies: true)
stub_feature_flags(group_level_security_policies: false)
end
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
......
......@@ -1934,4 +1934,62 @@ RSpec.describe GroupPolicy do
expect_disallowed(*owner_permissions)
end
end
describe 'security complience policy' do
context 'when licensed feature is available' do
before do
stub_licensed_features(security_orchestration_policies: false)
end
context 'with developer or maintainer role' do
where(role: %w[maintainer developer])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_disallowed(:security_orchestration_policies) }
it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
end
end
context 'with owner role' do
where(role: %w[owner])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_disallowed(:security_orchestration_policies) }
it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
end
end
end
context 'when licensed feature is available' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
context 'with developer or maintainer role' do
where(role: %w[maintainer developer])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:security_orchestration_policies) }
it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
end
end
context 'with owner role' do
where(role: %w[owner])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:security_orchestration_policies) }
it { is_expected.to be_allowed(:update_security_orchestration_policy_project) }
end
end
end
end
end
......@@ -2,12 +2,13 @@
require 'spec_helper'
RSpec.describe 'Assigns scan execution policy project to a project' do
RSpec.describe 'Assigns scan execution policy project to a project/namespace' do
include GraphqlHelpers
let_it_be_with_refind(:owner) { create(:user) }
let_it_be_with_refind(:user) { create(:user) }
let_it_be_with_refind(:maintainer) { create(:user) }
let_it_be_with_refind(:project) { create(:project, namespace: owner.namespace) }
let_it_be_with_refind(:namespace) { create(:group) }
let_it_be_with_refind(:policy_project) { create(:project) }
let_it_be_with_refind(:policy_project_id) { GitlabSchema.id_from_object(policy_project) }
......@@ -16,7 +17,7 @@ RSpec.describe 'Assigns scan execution policy project to a project' do
subject { post_graphql_mutation(mutation, current_user: current_user) }
def mutation
variables = { project_path: project.full_path, security_policy_project_id: policy_project_id.to_s }
variables = { full_path: container.full_path, security_policy_project_id: policy_project_id.to_s }
graphql_mutation(:security_policy_project_assign, variables) do
<<-QL.strip_heredoc
......@@ -29,48 +30,82 @@ RSpec.describe 'Assigns scan execution policy project to a project' do
graphql_mutation_response(:security_policy_project_assign)
end
context 'when licensed feature is available' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
shared_context 'assigns security policy project' do
context 'when licensed feature is available' do
before do
stub_licensed_features(security_orchestration_policies: true)
end
context 'when user is an owner of the container' do
before do
container.add_owner(owner)
end
context 'when user is an owner of the project' do
it 'assigns the security policy project', :aggregate_failures do
subject
it 'assigns the security policy project', :aggregate_failures do
subject
orchestration_policy_configuration = project.security_orchestration_policy_configuration
orchestration_policy_configuration = container.security_orchestration_policy_configuration
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
end
end
end
context 'when user is not an owner' do
let(:current_user) { user }
context 'when user is not an owner' do
let(:current_user) { maintainer }
before do
project.add_maintainer(user)
before do
project.add_maintainer(maintainer)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
context 'when policy_project_id is invalid' do
let_it_be_with_refind(:policy_project_id) { "gid://gitlab/Project/#{non_existing_record_id}" }
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
context 'when policy_project_id is invalid' do
let_it_be_with_refind(:policy_project_id) { "gid://gitlab/Project/#{non_existing_record_id}" }
context 'when feature is not licensed' do
before do
stub_licensed_features(security_orchestration_policies: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
context 'when feature is not licensed' do
before do
stub_licensed_features(security_orchestration_policies: false)
context 'for project' do
let(:container) { project }
it_behaves_like 'assigns security policy project'
end
context 'for namespace' do
let(:container) { namespace }
context 'when feature is enabled' do
before do
stub_feature_flags(group_level_security_policies: namespace)
end
it_behaves_like 'assigns security policy project'
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
context 'when feature is disabled' do
before do
stub_licensed_features(security_orchestration_policies: true)
stub_feature_flags(group_level_security_policies: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
end
......@@ -2,19 +2,20 @@
require 'spec_helper'
RSpec.describe 'Creates and assigns scan execution policy project to a project' do
RSpec.describe 'Creates and assigns scan execution policy project to a project/namespace' do
include GraphqlHelpers
let_it_be_with_refind(:owner) { create(:user) }
let_it_be_with_refind(:user) { create(:user) }
let_it_be_with_refind(:maintainer) { create(:user) }
let_it_be_with_refind(:project) { create(:project, namespace: owner.namespace) }
let_it_be_with_refind(:namespace) { create(:group) }
let(:current_user) { owner }
subject { post_graphql_mutation(mutation, current_user: current_user) }
def mutation
variables = { project_path: project.full_path }
variables = { full_path: container.full_path }
graphql_mutation(:security_policy_project_create, variables) do
<<-QL.strip_heredoc
......@@ -31,46 +32,80 @@ RSpec.describe 'Creates and assigns scan execution policy project to a project'
graphql_mutation_response(:security_policy_project_create)
end
context 'when licensed feature is available' do
before do
# TODO: investigate too many qeuries issue as part of Project Management Database and Query Performance
# Epic: https://gitlab.com/groups/gitlab-org/-/epics/5804
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/348344
stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 130)
stub_licensed_features(security_orchestration_policies: true)
end
shared_examples 'creates security policy project' do
context 'when licensed feature is available' do
before do
# TODO: investigate too many qeuries issue as part of Project Management Database and Query Performance
# Epic: https://gitlab.com/groups/gitlab-org/-/epics/5804
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/348344
stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 140)
stub_licensed_features(security_orchestration_policies: true)
end
context 'when user is an owner of the project' do
it 'creates and assigns the security policy project', :aggregate_failures do
expect { subject }.to change { ::Project.count }.by(1)
context 'when user is an owner of the container' do
it 'creates and assigns the security policy project', :aggregate_failures do
expect { subject }.to change { ::Project.count }.by(1)
orchestration_policy_configuration = project.security_orchestration_policy_configuration
orchestration_policy_configuration = container.security_orchestration_policy_configuration
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(mutation_response.dig('project', 'id')).to eq(orchestration_policy_configuration.security_policy_management_project.to_gid.to_s)
expect(mutation_response.dig('project', 'name')).to eq("#{project.name} - Security policy project")
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(mutation_response.dig('project', 'id')).to eq(orchestration_policy_configuration.security_policy_management_project.to_gid.to_s)
expect(mutation_response.dig('project', 'name')).to eq("#{container.name} - Security policy project")
end
end
end
context 'when user is not an owner' do
let(:current_user) { user }
context 'when user is not an owner' do
let(:current_user) { maintainer }
before do
project.add_maintainer(maintainer)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
context 'when feature is not licensed' do
before do
project.add_maintainer(user)
stub_licensed_features(security_orchestration_policies: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
context 'when feature is not licensed' do
context 'for project' do
let(:container) { project }
it_behaves_like 'creates security policy project'
end
context 'for namespace' do
let(:container) { namespace }
before do
stub_licensed_features(security_orchestration_policies: false)
namespace.add_owner(owner)
end
context 'when feature is enabled' do
before do
stub_feature_flags(group_level_security_policies: namespace)
end
it_behaves_like 'creates security policy project'
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
context 'when feature is disabled' do
before do
stub_licensed_features(security_orchestration_policies: true)
stub_feature_flags(group_level_security_policies: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
end
......@@ -373,18 +373,36 @@ RSpec.describe Projects::CreateService, '#execute' do
end
context 'security policy configuration' do
let_it_be(:security_policy_target_project) { create(:project) }
context 'with security_policy_target_project_id' do
let_it_be(:security_policy_target_project) { create(:project) }
before do
opts[:security_policy_target_project_id] = security_policy_target_project.id
before do
opts[:security_policy_target_project_id] = security_policy_target_project.id
stub_licensed_features(security_orchestration_policies: true)
end
stub_licensed_features(security_orchestration_policies: true)
it 'creates security policy configuration for the project' do
expect(::Security::Orchestration::AssignService).to receive_message_chain(:new, :execute)
create_project(user, opts)
end
end
it 'creates security policy configuration for the project' do
expect(::Security::Orchestration::AssignService).to receive_message_chain(:new, :execute)
context 'with security_policy_target_namespace_id' do
let_it_be(:security_policy_target_namespace) { create(:namespace) }
create_project(user, opts)
before do
opts[:security_policy_target_namespace_id] = security_policy_target_namespace.id
stub_licensed_features(security_orchestration_policies: true)
end
it 'creates security policy configuration for the project' do
expect(::Security::Orchestration::AssignService).to receive_message_chain(:new, :execute)
create_project(user, opts)
end
end
end
......
......@@ -5,76 +5,99 @@ require 'spec_helper'
RSpec.describe Security::Orchestration::AssignService do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:another_project) { create(:project) }
let_it_be(:namespace, reload: true) { create(:group) }
let_it_be(:another_namespace) { create(:group) }
let_it_be(:policy_project) { create(:project) }
let_it_be(:new_policy_project) { create(:project) }
let(:container) { project }
let(:another_container) { another_project }
describe '#execute' do
subject(:service) do
described_class.new(project, nil, policy_project_id: policy_project.id).execute
described_class.new(container: container, current_user: nil, params: { policy_project_id: policy_project.id }).execute
end
before do
service
end
it 'assigns policy project to project' do
expect(service).to be_success
expect(
project.security_orchestration_policy_configuration.security_policy_management_project_id
).to eq(policy_project.id)
end
shared_examples 'assigns policy project' do
it 'assigns policy project to container' do
expect(service).to be_success
expect(
container.security_orchestration_policy_configuration.security_policy_management_project_id
).to eq(policy_project.id)
end
it 'updates project with new policy project' do
repeated_service =
described_class.new(project, nil, policy_project_id: new_policy_project.id).execute
it 'updates container with new policy project' do
repeated_service =
described_class.new(container: container, current_user: nil, params: { policy_project_id: new_policy_project.id }).execute
expect(repeated_service).to be_success
expect(
project.security_orchestration_policy_configuration.security_policy_management_project_id
).to eq(new_policy_project.id)
end
expect(repeated_service).to be_success
expect(
container.security_orchestration_policy_configuration.security_policy_management_project_id
).to eq(new_policy_project.id)
end
it 'assigns same policy to different projects' do
repeated_service =
described_class.new(another_project, nil, policy_project_id: policy_project.id).execute
expect(repeated_service).to be_success
end
it 'assigns same policy to different container' do
repeated_service =
described_class.new(container: another_container, current_user: nil, params: { policy_project_id: policy_project.id }).execute
expect(repeated_service).to be_success
end
it 'unassigns project' do
expect { described_class.new(project, nil, policy_project_id: nil).execute }.to change {
project.reload.security_orchestration_policy_configuration
}.to(nil)
end
it 'unassigns project' do
expect { described_class.new(container: container, current_user: nil, params: { policy_project_id: nil }).execute }.to change {
container.reload.security_orchestration_policy_configuration
}.to(nil)
end
it 'returns error when db has problem' do
dbl_error = double('ActiveRecord')
dbl =
double(
'Security::OrchestrationPolicyConfiguration',
security_orchestration_policy_configuration: dbl_error
)
it 'returns error when db has problem' do
dbl_error = double('ActiveRecord')
dbl =
double(
'Security::OrchestrationPolicyConfiguration',
security_orchestration_policy_configuration: dbl_error
)
allow(dbl_error).to receive(:update!).and_raise(ActiveRecord::RecordInvalid)
allow(dbl_error).to receive(:update!).and_raise(ActiveRecord::RecordInvalid)
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:has_existing_policy?).and_return(true)
allow(instance).to receive(:container).and_return(dbl)
end
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:has_existing_policy?).and_return(true)
allow(instance).to receive(:project).and_return(dbl)
repeated_service =
described_class.new(container: container, current_user: nil, params: { policy_project_id: new_policy_project.id }).execute
expect(repeated_service).to be_error
end
repeated_service =
described_class.new(project, nil, policy_project_id: new_policy_project.id).execute
describe 'with invalid project id' do
subject(:service) { described_class.new(container: container, current_user: nil, params: { policy_project_id: 345 }).execute }
it 'does not change policy project' do
expect(service).to be_error
expect(repeated_service).to be_error
expect { service }.not_to change { container.security_orchestration_policy_configuration }
end
end
end
describe 'with invalid project id' do
subject(:service) { described_class.new(project, nil, policy_project_id: 345).execute }
context 'for project' do
let(:container) { project }
let(:another_container) { another_project }
it_behaves_like 'assigns policy project'
end
it 'assigns policy project to project' do
expect(service).to be_error
context 'for namespace' do
let(:container) { namespace }
let(:another_container) { another_namespace }
expect { service }.not_to change { project.security_orchestration_policy_configuration }
end
it_behaves_like 'assigns policy project'
end
end
end
......@@ -4,15 +4,18 @@ require 'spec_helper'
RSpec.describe Security::SecurityOrchestrationPolicies::ProjectCreateService do
describe '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { project.first_owner }
let_it_be_with_refind(:project) { create(:project) }
subject(:service) { described_class.new(project: project, current_user: current_user) }
let_it_be(:owner) { create(:user) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
context 'when security_orchestration_policies_configuration does not exist for project' do
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let(:current_user) { container.first_owner }
let(:container) { project }
subject(:service) { described_class.new(container: container, current_user: current_user) }
context 'when security_orchestration_policies_configuration does not exist for project' do
before do
project.add_maintainer(maintainer)
project.add_developer(developer)
......@@ -31,10 +34,32 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProjectCreateService do
end
end
context 'when security_orchestration_policies_configuration does not exist for namespace' do
let(:group) { create(:group) }
let(:container) { group }
before do
group.add_owner(owner)
group.add_maintainer(maintainer)
group.add_developer(developer)
end
it 'creates policy project with maintainers and developers from target group as developers', :aggregate_failures do
response = service.execute
policy_project = response[:policy_project]
expect(group.reload.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
expect(policy_project.namespace).to eq(group)
expect(policy_project.owner).to eq(group)
expect(MembersFinder.new(policy_project, nil).execute.map(&:user)).to contain_exactly(owner, maintainer, developer)
expect(policy_project.container_registry_access_level).to eq(ProjectFeature::DISABLED)
expect(policy_project.repository.readme.data).to include('# Security Policy Project for')
expect(policy_project.repository.readme.data).to include('## Default branch protection settings')
end
end
context 'when adding users to security policy project fails' do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { project.first_owner }
let_it_be(:maintainer) { create(:user) }
let(:current_user) { project.first_owner }
before do
project.add_maintainer(maintainer)
......@@ -54,8 +79,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProjectCreateService do
end
context 'when project creation fails' do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let(:current_user) { create(:user) }
it 'returns error' do
response = service.execute
......
......@@ -10309,7 +10309,7 @@ msgstr ""
msgid "Could not upload your designs as one or more files uploaded are not supported."
msgstr ""
msgid "Couldn't assign policy to project"
msgid "Couldn't assign policy to project or group"
msgstr ""
msgid "Country"
......
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