Commit 0ae53f72 authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Markus Koller

Prevent DAST profiles from being deleted when referenced by policies

This change adds new fields to GraphQL API and adds additional
contraint that verifies if given DAST Site/Scanner profile can be
deleted if is referenced by active policy
parent 5ffe0ec9
...@@ -1302,6 +1302,7 @@ Represents a DAST scanner profile. ...@@ -1302,6 +1302,7 @@ Represents a DAST scanner profile.
| `globalId` **{warning-solid}** | DastScannerProfileID! | **Deprecated:** Use `id`. Deprecated in 13.6. | | `globalId` **{warning-solid}** | DastScannerProfileID! | **Deprecated:** Use `id`. Deprecated in 13.6. |
| `id` | DastScannerProfileID! | ID of the DAST scanner profile. | | `id` | DastScannerProfileID! | ID of the DAST scanner profile. |
| `profileName` | String | Name of the DAST scanner profile. | | `profileName` | String | Name of the DAST scanner profile. |
| `referencedInSecurityPolicies` | String! => Array | List of security policy names that are referencing given project. |
| `scanType` | DastScanTypeEnum | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. | | `scanType` | DastScanTypeEnum | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. |
| `showDebugMessages` | Boolean! | Indicates if debug messages should be included in DAST console output. True to include the debug messages. | | `showDebugMessages` | Boolean! | Indicates if debug messages should be included in DAST console output. True to include the debug messages. |
| `spiderTimeout` | Int | The maximum number of minutes allowed for the spider to traverse the site. | | `spiderTimeout` | Int | The maximum number of minutes allowed for the spider to traverse the site. |
...@@ -1348,6 +1349,7 @@ Represents a DAST Site Profile. ...@@ -1348,6 +1349,7 @@ Represents a DAST Site Profile.
| `id` | DastSiteProfileID! | ID of the site profile. | | `id` | DastSiteProfileID! | ID of the site profile. |
| `normalizedTargetUrl` | String | Normalized URL of the target to be scanned. | | `normalizedTargetUrl` | String | Normalized URL of the target to be scanned. |
| `profileName` | String | The name of the site profile. | | `profileName` | String | The name of the site profile. |
| `referencedInSecurityPolicies` | String! => Array | List of security policy names that are referencing given project. |
| `targetUrl` | String | The URL of the target to be scanned. | | `targetUrl` | String | The URL of the target to be scanned. |
| `userPermissions` | DastSiteProfilePermissions! | Permissions for the current user on the resource | | `userPermissions` | DastSiteProfilePermissions! | Permissions for the current user on the resource |
| `validationStatus` | DastSiteProfileValidationStatusEnum | The current validation status of the site profile. | | `validationStatus` | DastSiteProfileValidationStatusEnum | The current validation status of the site profile. |
......
...@@ -21,13 +21,12 @@ module Mutations ...@@ -21,13 +21,12 @@ module Mutations
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::DastSiteProfile].coerce_isolated_input(id) id = ::Types::GlobalIDType[::DastSiteProfile].coerce_isolated_input(id)
dast_site_profile = find_dast_site_profile(project: project, global_id: id) service = ::DastSiteProfiles::DestroyService.new(project, current_user)
result = service.execute(id: id.model_id)
return { errors: dast_site_profile.errors.full_messages } unless dast_site_profile.destroy return { errors: result.errors } unless result.success?
{ errors: [] } { errors: [] }
rescue ActiveRecord::RecordNotFound
raise_resource_not_available_error!
end end
private private
...@@ -35,12 +34,6 @@ module Mutations ...@@ -35,12 +34,6 @@ module Mutations
def find_object(full_path) def find_object(full_path)
Project.find_by_full_path(full_path) Project.find_by_full_path(full_path)
end end
def find_dast_site_profile(project:, global_id:)
project.dast_site_profiles.find(global_id.model_id)
rescue ActiveRecord::RecordNotFound
raise_resource_not_available_error!
end
end end
end end
end end
...@@ -40,6 +40,11 @@ module Types ...@@ -40,6 +40,11 @@ module Types
field :edit_path, GraphQL::STRING_TYPE, null: true, field :edit_path, GraphQL::STRING_TYPE, null: true,
description: 'Relative web path to the edit page of a scanner profile.' description: 'Relative web path to the edit page of a scanner profile.'
field :referenced_in_security_policies, [GraphQL::STRING_TYPE], null: true,
complexity: 10,
calls_gitaly: true,
description: 'List of security policy names that are referencing given project.'
def edit_path def edit_path
Rails.application.routes.url_helpers.edit_project_security_configuration_dast_profiles_dast_scanner_profile_path(object.project, object) Rails.application.routes.url_helpers.edit_project_security_configuration_dast_profiles_dast_scanner_profile_path(object.project, object)
end end
......
...@@ -29,6 +29,11 @@ module Types ...@@ -29,6 +29,11 @@ module Types
field :normalized_target_url, GraphQL::STRING_TYPE, null: true, field :normalized_target_url, GraphQL::STRING_TYPE, null: true,
description: 'Normalized URL of the target to be scanned.' description: 'Normalized URL of the target to be scanned.'
field :referenced_in_security_policies, [GraphQL::STRING_TYPE], null: true,
complexity: 10,
calls_gitaly: true,
description: 'List of security policy names that are referencing given project.'
def target_url def target_url
object.dast_site.url object.dast_site.url
end end
......
...@@ -17,4 +17,10 @@ class DastScannerProfile < ApplicationRecord ...@@ -17,4 +17,10 @@ class DastScannerProfile < ApplicationRecord
def full_scan_enabled? def full_scan_enabled?
scan_type == 'active' scan_type == 'active'
end end
def referenced_in_security_policies
return [] unless project.security_orchestration_policy_configuration.present?
project.security_orchestration_policy_configuration.active_policy_names_with_dast_scanner_profile(name)
end
end end
...@@ -21,6 +21,12 @@ class DastSiteProfile < ApplicationRecord ...@@ -21,6 +21,12 @@ class DastSiteProfile < ApplicationRecord
dast_site_validation.state dast_site_validation.state
end end
def referenced_in_security_policies
return [] unless project.security_orchestration_policy_configuration.present?
project.security_orchestration_policy_configuration.active_policy_names_with_dast_site_profile(name)
end
private private
def cleanup_dast_site def cleanup_dast_site
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module Security module Security
class OrchestrationPolicyConfiguration < ApplicationRecord class OrchestrationPolicyConfiguration < ApplicationRecord
include Gitlab::Utils::StrongMemoize
self.table_name = 'security_orchestration_policy_configurations' self.table_name = 'security_orchestration_policy_configurations'
POLICIES_BASE_PATH = '.gitlab/security-policies/' POLICIES_BASE_PATH = '.gitlab/security-policies/'
...@@ -14,7 +16,13 @@ module Security ...@@ -14,7 +16,13 @@ module Security
validates :project, presence: true, uniqueness: true validates :project, presence: true, uniqueness: true
validates :security_policy_management_project, presence: true, uniqueness: true validates :security_policy_management_project, presence: true, uniqueness: true
def enabled?
::Feature.enabled?(:security_orchestration_policies_configuration, project)
end
def active_policies def active_policies
return [] unless enabled?
security_policy_management_project security_policy_management_project
.repository .repository
.ls_files(security_policy_management_project.default_branch_or_master) .ls_files(security_policy_management_project.default_branch_or_master)
...@@ -30,8 +38,33 @@ module Security ...@@ -30,8 +38,33 @@ module Security
.select { |action| action[:scan].in?(ON_DEMAND_SCANS) } .select { |action| action[:scan].in?(ON_DEMAND_SCANS) }
end end
def active_policy_names_with_dast_site_profile(profile_name)
active_policy_names_with_dast_profiles.dig(:site_profiles, profile_name)
end
def active_policy_names_with_dast_scanner_profile(profile_name)
active_policy_names_with_dast_profiles.dig(:scanner_profiles, profile_name)
end
private private
def active_policy_names_with_dast_profiles
strong_memoize(:active_policy_names_with_dast_profiles) do
profiles = { site_profiles: Hash.new { Set.new }, scanner_profiles: Hash.new { Set.new } }
active_policies.each do |policy|
policy[:actions].each do |action|
next unless action[:scan].in?(ON_DEMAND_SCANS)
profiles[:site_profiles][action[:site_profile]] += [policy[:name]]
profiles[:scanner_profiles][action[:scanner_profile]] += [policy[:name]] if action[:scanner_profile].present?
end
end
profiles
end
end
def policy_at(path) def policy_at(path)
security_policy_management_project security_policy_management_project
.repository .repository
......
...@@ -9,6 +9,7 @@ module DastScannerProfiles ...@@ -9,6 +9,7 @@ module DastScannerProfiles
dast_scanner_profile = find_dast_scanner_profile(id) dast_scanner_profile = find_dast_scanner_profile(id)
return ServiceResponse.error(message: "Scanner profile not found for given parameters") unless dast_scanner_profile return ServiceResponse.error(message: "Scanner profile not found for given parameters") unless dast_scanner_profile
return ServiceResponse.error(message: "Cannot delete #{dast_scanner_profile.name} referenced in security policy") if referenced_in_security_policy?(dast_scanner_profile)
if dast_scanner_profile.destroy if dast_scanner_profile.destroy
ServiceResponse.success(payload: dast_scanner_profile) ServiceResponse.success(payload: dast_scanner_profile)
...@@ -23,6 +24,10 @@ module DastScannerProfiles ...@@ -23,6 +24,10 @@ module DastScannerProfiles
::ServiceResponse.error(message: _('You are not authorized to update this scanner profile'), http_status: 403) ::ServiceResponse.error(message: _('You are not authorized to update this scanner profile'), http_status: 403)
end end
def referenced_in_security_policy?(profile)
profile.referenced_in_security_policies.present?
end
def can_delete_scanner_profile? def can_delete_scanner_profile?
can?(current_user, :create_on_demand_dast_scan, project) can?(current_user, :create_on_demand_dast_scan, project)
end end
......
...@@ -9,6 +9,7 @@ module DastScannerProfiles ...@@ -9,6 +9,7 @@ module DastScannerProfiles
dast_scanner_profile = find_dast_scanner_profile(id) dast_scanner_profile = find_dast_scanner_profile(id)
return ServiceResponse.error(message: "Scanner profile not found for given parameters") unless dast_scanner_profile return ServiceResponse.error(message: "Scanner profile not found for given parameters") unless dast_scanner_profile
return ServiceResponse.error(message: "Cannot modify #{dast_scanner_profile.name} referenced in security policy") if referenced_in_security_policy?(dast_scanner_profile)
update_args = { update_args = {
name: profile_name, name: profile_name,
...@@ -32,6 +33,10 @@ module DastScannerProfiles ...@@ -32,6 +33,10 @@ module DastScannerProfiles
::ServiceResponse.error(message: _('You are not authorized to update this scanner profile'), http_status: 403) ::ServiceResponse.error(message: _('You are not authorized to update this scanner profile'), http_status: 403)
end end
def referenced_in_security_policy?(profile)
profile.referenced_in_security_policies.present?
end
def can_update_scanner_profile? def can_update_scanner_profile?
can?(current_user, :create_on_demand_dast_scan, project) can?(current_user, :create_on_demand_dast_scan, project)
end end
......
# frozen_string_literal: true
module DastSiteProfiles
class DestroyService < BaseService
include Gitlab::Allowable
def execute(id:)
return unauthorized unless can_delete_site_profile?
dast_site_profile = find_dast_site_profile(id)
return ServiceResponse.error(message: "Site profile not found for given parameters") unless dast_site_profile
return ServiceResponse.error(message: "Cannot delete #{dast_site_profile.name} referenced in security policy") if referenced_in_security_policy?(dast_site_profile)
if dast_site_profile.destroy
ServiceResponse.success(payload: dast_site_profile)
else
ServiceResponse.error(message: 'Site profile failed to delete')
end
end
private
def unauthorized
::ServiceResponse.error(message: _('You are not authorized to delete this site profile'), http_status: 403)
end
def referenced_in_security_policy?(profile)
profile.referenced_in_security_policies.present?
end
def can_delete_site_profile?
can?(current_user, :create_on_demand_dast_scan, project)
end
def find_dast_site_profile(id)
project.dast_site_profiles.id_in(id).first
end
end
end
...@@ -5,9 +5,11 @@ module DastSiteProfiles ...@@ -5,9 +5,11 @@ module DastSiteProfiles
def execute(id:, profile_name:, target_url:) def execute(id:, profile_name:, target_url:)
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed? return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
ActiveRecord::Base.transaction do
dast_site_profile = find_dast_site_profile!(id) dast_site_profile = find_dast_site_profile!(id)
return ServiceResponse.error(message: "Cannot modify #{dast_site_profile.name} referenced in security policy") if referenced_in_security_policy?(dast_site_profile)
ActiveRecord::Base.transaction do
service = DastSites::FindOrCreateService.new(project, current_user) service = DastSites::FindOrCreateService.new(project, current_user)
dast_site = service.execute!(url: target_url) dast_site = service.execute!(url: target_url)
...@@ -28,6 +30,10 @@ module DastSiteProfiles ...@@ -28,6 +30,10 @@ module DastSiteProfiles
Ability.allowed?(current_user, :create_on_demand_dast_scan, project) Ability.allowed?(current_user, :create_on_demand_dast_scan, project)
end end
def referenced_in_security_policy?(profile)
profile.referenced_in_security_policies.present?
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_dast_site_profile!(id) def find_dast_site_profile!(id)
DastSiteProfilesFinder.new(project_id: project.id, id: id).execute.first! DastSiteProfilesFinder.new(project_id: project.id, id: id).execute.first!
......
...@@ -45,9 +45,9 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do ...@@ -45,9 +45,9 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do
context 'when there is an issue deleting the dast_site_profile' do context 'when there is an issue deleting the dast_site_profile' do
it 'returns an error' do it 'returns an error' do
allow(mutation).to receive(:find_dast_site_profile).and_return(dast_site_profile) allow_next_instance_of(::DastSiteProfiles::DestroyService) do |service|
allow(dast_site_profile).to receive(:destroy).and_return(false) allow(service).to receive(:execute).and_return(double(success?: false, errors: ['Name is weird']))
dast_site_profile.errors.add(:name, 'is weird') end
expect(subject[:errors]).to include('Name is weird') expect(subject[:errors]).to include('Name is weird')
end end
......
...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do ...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do
let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile) } let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile) }
let_it_be(:project) { dast_scanner_profile.project } let_it_be(:project) { dast_scanner_profile.project }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:fields) { %i[id globalId profileName spiderTimeout targetTimeout editPath scanType useAjaxSpider showDebugMessages] } let_it_be(:fields) { %i[id globalId profileName spiderTimeout targetTimeout editPath scanType useAjaxSpider showDebugMessages referencedInSecurityPolicies] }
let(:response) do let(:response) do
GitlabSchema.execute( GitlabSchema.execute(
...@@ -50,6 +50,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do ...@@ -50,6 +50,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do
scanType scanType
useAjaxSpider useAjaxSpider
showDebugMessages showDebugMessages
referencedInSecurityPolicies
} }
} }
} }
......
...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do ...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do
let_it_be(:dast_site_profile) { create(:dast_site_profile) } let_it_be(:dast_site_profile) { create(:dast_site_profile) }
let_it_be(:project) { dast_site_profile.project } let_it_be(:project) { dast_site_profile.project }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:fields) { %i[id profileName targetUrl editPath validationStatus userPermissions normalizedTargetUrl] } let_it_be(:fields) { %i[id profileName targetUrl editPath validationStatus userPermissions normalizedTargetUrl referencedInSecurityPolicies] }
subject do subject do
GitlabSchema.execute( GitlabSchema.execute(
...@@ -47,6 +47,7 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do ...@@ -47,6 +47,7 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do
editPath editPath
validationStatus validationStatus
normalizedTargetUrl normalizedTargetUrl
referencedInSecurityPolicies
} }
} }
} }
......
...@@ -46,4 +46,30 @@ RSpec.describe DastScannerProfile, type: :model do ...@@ -46,4 +46,30 @@ RSpec.describe DastScannerProfile, type: :model do
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
end end
end end
describe '#referenced_in_security_policies' do
context 'there is no security_orchestration_policy_configuration assigned to project' do
it 'returns the referenced policy name' do
expect(subject.referenced_in_security_policies).to eq([])
end
end
context 'there is security_orchestration_policy_configuration assigned to project' do
let(:security_orchestration_policy_configuration) { instance_double(Security::OrchestrationPolicyConfiguration, present?: true, active_policy_names_with_dast_scanner_profile: ['Policy Name']) }
before do
allow(subject.project).to receive(:security_orchestration_policy_configuration).and_return(security_orchestration_policy_configuration)
end
it 'calls security_orchestration_policy_configuration.active_policy_names_with_dast_scanner_profile with profile name' do
expect(security_orchestration_policy_configuration).to receive(:active_policy_names_with_dast_scanner_profile).with(subject.name)
subject.referenced_in_security_policies
end
it 'returns empty array' do
expect(subject.referenced_in_security_policies).to eq(['Policy Name'])
end
end
end
end end
...@@ -98,4 +98,30 @@ RSpec.describe DastSiteProfile, type: :model do ...@@ -98,4 +98,30 @@ RSpec.describe DastSiteProfile, type: :model do
end end
end end
end end
describe '#referenced_in_security_policies' do
context 'there is no security_orchestration_policy_configuration assigned to project' do
it 'returns empty array' do
expect(subject.referenced_in_security_policies).to eq([])
end
end
context 'there is security_orchestration_policy_configuration assigned to project' do
let(:security_orchestration_policy_configuration) { instance_double(Security::OrchestrationPolicyConfiguration, present?: true, active_policy_names_with_dast_site_profile: ['Policy Name']) }
before do
allow(subject.project).to receive(:security_orchestration_policy_configuration).and_return(security_orchestration_policy_configuration)
end
it 'calls security_orchestration_policy_configuration.active_policy_names_with_dast_site_profile with profile name' do
expect(security_orchestration_policy_configuration).to receive(:active_policy_names_with_dast_site_profile).with(subject.name)
subject.referenced_in_security_policies
end
it 'returns the referenced policy name' do
expect(subject.referenced_in_security_policies).to eq(['Policy Name'])
end
end
end
end end
...@@ -24,6 +24,22 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -24,6 +24,22 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
it { is_expected.to validate_uniqueness_of(:security_policy_management_project) } it { is_expected.to validate_uniqueness_of(:security_policy_management_project) }
end end
describe '#enabled?' do
subject { security_orchestration_policy_configuration.enabled? }
context 'when feature is enabled' do
it { is_expected.to eq(true) }
end
context 'when feature is disabled' do
before do
stub_feature_flags(security_orchestration_policies_configuration: false)
end
it { is_expected.to eq(false) }
end
end
describe '#active_policies' do describe '#active_policies' do
let(:enforce_dast_yaml) do let(:enforce_dast_yaml) do
<<-EOS <<-EOS
...@@ -86,6 +102,16 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -86,6 +102,16 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
it 'returns only enabled policies' do it 'returns only enabled policies' do
expect(active_policies).to eq(expected_active_policies) expect(active_policies).to eq(expected_active_policies)
end end
context 'when feature is disabled' do
before do
stub_feature_flags(security_orchestration_policies_configuration: false)
end
it 'returns empty array' do
expect(active_policies).to eq([])
end
end
end end
describe '#on_demand_scan_actions' do describe '#on_demand_scan_actions' do
...@@ -173,4 +199,68 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do ...@@ -173,4 +199,68 @@ RSpec.describe Security::OrchestrationPolicyConfiguration do
expect(on_demand_scan_actions).to eq(expected_actions) expect(on_demand_scan_actions).to eq(expected_actions)
end end
end end
describe '#active_policy_names_with_dast_site_profile' do
let(:enforce_dast_yaml) do
<<-EOS
type: scan_execution_policy
name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project
enabled: true
rules:
- type: pipeline
branches:
- "production"
actions:
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile 2
EOS
end
before do
allow(security_policy_management_project).to receive(:repository).and_return(repository)
allow(repository).to receive(:ls_files).and_return(['.gitlab/security-policies/enforce-dast.yml'])
allow(repository).to receive(:blob_data_at).with(default_branch, '.gitlab/security-policies/enforce-dast.yml').and_return(enforce_dast_yaml)
end
it 'returns list of policy names where site profile is referenced' do
expect(security_orchestration_policy_configuration.active_policy_names_with_dast_site_profile('Site Profile')).to contain_exactly('Run DAST in every pipeline')
end
end
describe '#active_policy_names_with_dast_scanner_profile' do
let(:enforce_dast_yaml) do
<<-EOS
type: scan_execution_policy
name: Run DAST in every pipeline
description: This policy enforces to run DAST for every pipeline within the project
enabled: true
rules:
- type: pipeline
branches:
- "production"
actions:
- scan: dast
site_profile: Site Profile
scanner_profile: Scanner Profile
- scan: dast
site_profile: Site Profile 2
scanner_profile: Scanner Profile
EOS
end
before do
allow(security_policy_management_project).to receive(:repository).and_return(repository)
allow(repository).to receive(:ls_files).and_return(['.gitlab/security-policies/enforce-dast.yml'])
allow(repository).to receive(:blob_data_at).with(default_branch, '.gitlab/security-policies/enforce-dast.yml').and_return(enforce_dast_yaml)
end
it 'returns list of policy names where site profile is referenced' do
expect(security_orchestration_policy_configuration.active_policy_names_with_dast_scanner_profile('Scanner Profile')).to contain_exactly('Run DAST in every pipeline')
end
end
end end
...@@ -8,11 +8,12 @@ RSpec.describe 'Creating a DAST Site Profile' do ...@@ -8,11 +8,12 @@ RSpec.describe 'Creating a DAST Site Profile' do
let!(:dast_site_profile) { create(:dast_site_profile, project: project) } let!(:dast_site_profile) { create(:dast_site_profile, project: project) }
let(:mutation_name) { :dast_site_profile_delete } let(:mutation_name) { :dast_site_profile_delete }
let(:dast_site_profile_id) { dast_site_profile.to_global_id.to_s }
let(:mutation) do let(:mutation) do
graphql_mutation( graphql_mutation(
mutation_name, mutation_name,
full_path: full_path, full_path: full_path,
id: dast_site_profile.to_global_id.to_s id: dast_site_profile_id
) )
end end
...@@ -24,22 +25,18 @@ RSpec.describe 'Creating a DAST Site Profile' do ...@@ -24,22 +25,18 @@ RSpec.describe 'Creating a DAST Site Profile' do
context 'when there is an issue deleting the dast_site_profile' do context 'when there is an issue deleting the dast_site_profile' do
before do before do
mutation_klass = Mutations::DastSiteProfiles::Delete allow_next_instance_of(::DastSiteProfiles::DestroyService) do |service|
allow_any_instance_of(mutation_klass).to receive(:find_dast_site_profile).and_return(dast_site_profile) allow(service).to receive(:execute).and_return(double(success?: false, errors: ['Name is weird']))
allow(dast_site_profile).to receive(:destroy).and_return(false) end
dast_site_profile.errors.add(:name, 'is weird')
end end
it_behaves_like 'a mutation that returns errors in the response', errors: ['Name is weird'] it_behaves_like 'a mutation that returns errors in the response', errors: ['Name is weird']
end end
context 'when the dast_site_profile does not exist' do context 'when the dast_site_profile does not exist' do
before do let(:dast_site_profile_id) { Gitlab::GlobalId.build(nil, model_name: 'DastSiteProfile', id: 'does_not_exist') }
dast_site_profile.destroy!
end
it_behaves_like 'a mutation that returns top-level errors', it_behaves_like 'a mutation that returns errors in the response', errors: ['Site profile not found for given parameters']
errors: [::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end end
context 'when wrong type of global id is passed' do context 'when wrong type of global id is passed' do
......
...@@ -4,8 +4,8 @@ require 'spec_helper' ...@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe DastScannerProfiles::DestroyService do RSpec.describe DastScannerProfiles::DestroyService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:dast_scanner_profile, reload: true) { create(:dast_scanner_profile, target_timeout: 200, spider_timeout: 5000) } let_it_be(:dast_profile, reload: true) { create(:dast_scanner_profile, target_timeout: 200, spider_timeout: 5000) }
let(:project) { dast_scanner_profile.project } let(:project) { dast_profile.project }
before do before do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
...@@ -18,7 +18,7 @@ RSpec.describe DastScannerProfiles::DestroyService do ...@@ -18,7 +18,7 @@ RSpec.describe DastScannerProfiles::DestroyService do
) )
end end
let(:dast_scanner_profile_id) { dast_scanner_profile.id } let(:dast_scanner_profile_id) { dast_profile.id }
let(:status) { subject.status } let(:status) { subject.status }
let(:message) { subject.message } let(:message) { subject.message }
let(:payload) { subject.payload } let(:payload) { subject.payload }
...@@ -77,6 +77,8 @@ RSpec.describe DastScannerProfiles::DestroyService do ...@@ -77,6 +77,8 @@ RSpec.describe DastScannerProfiles::DestroyService do
expect(message).to eq('You are not authorized to update this scanner profile') expect(message).to eq('You are not authorized to update this scanner profile')
end end
end end
include_examples 'restricts modification if referenced by policy', :delete
end end
end end
end end
...@@ -4,17 +4,17 @@ require 'spec_helper' ...@@ -4,17 +4,17 @@ require 'spec_helper'
RSpec.describe DastScannerProfiles::UpdateService do RSpec.describe DastScannerProfiles::UpdateService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:dast_scanner_profile, reload: true) { create(:dast_scanner_profile, target_timeout: 200, spider_timeout: 5000) } let_it_be(:dast_profile, reload: true) { create(:dast_scanner_profile, target_timeout: 200, spider_timeout: 5000) }
let_it_be(:dast_scanner_profile_2, reload: true) { create(:dast_scanner_profile) } let_it_be(:dast_profile_2, reload: true) { create(:dast_scanner_profile) }
let(:project) { dast_scanner_profile.project } let(:project) { dast_profile.project }
let(:project_2) { dast_scanner_profile_2.project } let(:project_2) { dast_profile_2.project }
let_it_be(:new_profile_name) { SecureRandom.hex } let_it_be(:new_profile_name) { SecureRandom.hex }
let_it_be(:new_target_timeout) { dast_scanner_profile.target_timeout + 1 } let_it_be(:new_target_timeout) { dast_profile.target_timeout + 1 }
let_it_be(:new_spider_timeout) { dast_scanner_profile.spider_timeout + 1 } let_it_be(:new_spider_timeout) { dast_profile.spider_timeout + 1 }
let_it_be(:new_scan_type) { (DastScannerProfile.scan_types.keys - [DastScannerProfile.last.scan_type]).first } let_it_be(:new_scan_type) { (DastScannerProfile.scan_types.keys - [DastScannerProfile.last.scan_type]).first }
let(:new_use_ajax_spider) { !dast_scanner_profile.use_ajax_spider } let(:new_use_ajax_spider) { !dast_profile.use_ajax_spider }
let(:new_show_debug_messages) { !dast_scanner_profile.show_debug_messages } let(:new_show_debug_messages) { !dast_profile.show_debug_messages }
before do before do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
...@@ -33,7 +33,7 @@ RSpec.describe DastScannerProfiles::UpdateService do ...@@ -33,7 +33,7 @@ RSpec.describe DastScannerProfiles::UpdateService do
) )
end end
let(:dast_scanner_profile_id) { dast_scanner_profile.id } let(:dast_scanner_profile_id) { dast_profile.id }
let(:status) { subject.status } let(:status) { subject.status }
let(:message) { subject.message } let(:message) { subject.message }
let(:payload) { subject.payload } let(:payload) { subject.payload }
...@@ -56,7 +56,7 @@ RSpec.describe DastScannerProfiles::UpdateService do ...@@ -56,7 +56,7 @@ RSpec.describe DastScannerProfiles::UpdateService do
subject do subject do
described_class.new(project_2, user).execute( described_class.new(project_2, user).execute(
id: dast_scanner_profile.id, id: dast_profile.id,
profile_name: new_profile_name, profile_name: new_profile_name,
target_timeout: new_target_timeout, target_timeout: new_target_timeout,
spider_timeout: new_spider_timeout, spider_timeout: new_spider_timeout,
...@@ -94,9 +94,9 @@ RSpec.describe DastScannerProfiles::UpdateService do ...@@ -94,9 +94,9 @@ RSpec.describe DastScannerProfiles::UpdateService do
updated_dast_scanner_profile = payload.reload updated_dast_scanner_profile = payload.reload
aggregate_failures do aggregate_failures do
expect(updated_dast_scanner_profile.scan_type).to eq(dast_scanner_profile.scan_type) expect(updated_dast_scanner_profile.scan_type).to eq(dast_profile.scan_type)
expect(updated_dast_scanner_profile.use_ajax_spider).to eq(dast_scanner_profile.use_ajax_spider) expect(updated_dast_scanner_profile.use_ajax_spider).to eq(dast_profile.use_ajax_spider)
expect(updated_dast_scanner_profile.show_debug_messages).to eq(dast_scanner_profile.show_debug_messages) expect(updated_dast_scanner_profile.show_debug_messages).to eq(dast_profile.show_debug_messages)
end end
end end
end end
...@@ -164,6 +164,8 @@ RSpec.describe DastScannerProfiles::UpdateService do ...@@ -164,6 +164,8 @@ RSpec.describe DastScannerProfiles::UpdateService do
expect(message).to eq('You are not authorized to update this scanner profile') expect(message).to eq('You are not authorized to update this scanner profile')
end end
end end
include_examples 'restricts modification if referenced by policy', :modify
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DastSiteProfiles::DestroyService do
let_it_be(:user) { create(:user) }
let_it_be(:dast_profile, reload: true) { create(:dast_site_profile) }
let(:project) { dast_profile.project }
before do
stub_licensed_features(security_on_demand_scans: true)
end
describe '#execute' do
subject do
described_class.new(project, user).execute(
id: dast_site_profile_id
)
end
let(:dast_site_profile_id) { dast_profile.id }
let(:status) { subject.status }
let(:message) { subject.message }
let(:payload) { subject.payload }
context 'when a user does not have access to the project' do
it 'returns an error status' do
expect(status).to eq(:error)
end
it 'populates message' do
expect(message).to eq('You are not authorized to delete this site profile')
end
end
context 'when the user can run a DAST scan' do
before do
project.add_developer(user)
end
it 'returns a success status' do
expect(status).to eq(:success)
end
it 'deletes the dast_site_profile' do
expect { subject }.to change { DastSiteProfile.count }.by(-1)
end
it 'returns a dast_site_profile payload' do
expect(payload).to be_a(DastSiteProfile)
end
context 'when the dast_site_profile does not exist' do
let(:dast_site_profile_id) do
Gitlab::GlobalId.build(nil, model_name: 'DastSiteProfile', id: 'does_not_exist')
end
it 'returns an error status' do
expect(status).to eq(:error)
end
it 'populates message' do
expect(message).to eq('Site profile not found for given parameters')
end
end
context 'when on demand scan licensed feature is not available' do
before do
stub_licensed_features(security_on_demand_scans: false)
end
it 'returns an error status' do
expect(status).to eq(:error)
end
it 'populates message' do
expect(message).to eq('You are not authorized to delete this site profile')
end
end
include_examples 'restricts modification if referenced by policy', :delete
end
end
end
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe DastSiteProfiles::UpdateService do RSpec.describe DastSiteProfiles::UpdateService do
let(:project) { dast_site_profile.project } let(:project) { dast_profile.project }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:dast_site_profile) { create(:dast_site_profile) } let(:dast_profile) { create(:dast_site_profile) }
let(:new_profile_name) { SecureRandom.hex } let(:new_profile_name) { SecureRandom.hex }
let(:new_target_url) { generate(:url) } let(:new_target_url) { generate(:url) }
...@@ -17,7 +17,7 @@ RSpec.describe DastSiteProfiles::UpdateService do ...@@ -17,7 +17,7 @@ RSpec.describe DastSiteProfiles::UpdateService do
describe '#execute' do describe '#execute' do
subject do subject do
described_class.new(project, user).execute( described_class.new(project, user).execute(
id: dast_site_profile.id, id: dast_profile.id,
profile_name: new_profile_name, profile_name: new_profile_name,
target_url: new_target_url target_url: new_target_url
) )
...@@ -74,7 +74,7 @@ RSpec.describe DastSiteProfiles::UpdateService do ...@@ -74,7 +74,7 @@ RSpec.describe DastSiteProfiles::UpdateService do
context 'when the dast_site_profile doesn\'t exist' do context 'when the dast_site_profile doesn\'t exist' do
before do before do
dast_site_profile.destroy! dast_profile.destroy!
end end
it 'returns an error status' do it 'returns an error status' do
...@@ -99,6 +99,8 @@ RSpec.describe DastSiteProfiles::UpdateService do ...@@ -99,6 +99,8 @@ RSpec.describe DastSiteProfiles::UpdateService do
expect(message).to eq('Insufficient permissions') expect(message).to eq('Insufficient permissions')
end end
end end
include_examples 'restricts modification if referenced by policy', :modify
end end
end end
end end
# frozen_string_literal: true
RSpec.shared_examples 'restricts modification if referenced by policy' do |modification_type|
context 'when project has security policies enabled' do
before do
allow_next_found_instance_of(dast_profile.class) do |profile|
allow(profile).to receive(:referenced_in_security_policies).and_return(policy_names)
end
end
context 'when there is no policy that is referencing the profile' do
let(:policy_names) { [] }
it 'returns a success status' do
expect(status).to eq(:success)
end
end
context 'when there is a policy that is referencing the profile' do
let(:policy_names) { [dast_profile.name] }
it 'returns an error status' do
expect(status).to eq(:error)
end
it 'populates message' do
expect(message).to include("Cannot #{modification_type}")
end
end
end
end
...@@ -33870,6 +33870,9 @@ msgstr "" ...@@ -33870,6 +33870,9 @@ msgstr ""
msgid "You are not allowed to unlink your primary login account" msgid "You are not allowed to unlink your primary login account"
msgstr "" msgstr ""
msgid "You are not authorized to delete this site profile"
msgstr ""
msgid "You are not authorized to perform this action" msgid "You are not authorized to perform this action"
msgstr "" msgstr ""
......
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