Commit 6317ab84 authored by Philip Cunningham's avatar Philip Cunningham Committed by Mikołaj Wawrzyniak

Define CI Pipeline and DAST Profile relationship

- Add habtm relationship between Dast::Profile and Ci::Pipeline
- Amend services to rely on Dast::Profile and create association
- Add EE Contextable override to retrieve Dast::Profile variables
- Extract shared example
parent ba818874
---
title: Create database table dast_profiles_pipelines
merge_request: 56821
author:
type: added
# frozen_string_literal: true
class CreateDastProfilesPipelines < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
table_comment = { owner: 'group::dynamic analysis', description: 'Join table between DAST Profiles and CI Pipelines' }
create_table :dast_profiles_pipelines, primary_key: [:dast_profile_id, :ci_pipeline_id], comment: table_comment.to_json do |t|
t.bigint :dast_profile_id, null: false
t.bigint :ci_pipeline_id, null: false
t.index :ci_pipeline_id, unique: true, name: :index_dast_profiles_pipelines_on_ci_pipeline_id
end
end
def down
drop_table :dast_profiles_pipelines
end
end
# frozen_string_literal: true
class AddDastProfileIdFkToDastProfilesPipelines < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :dast_profiles_pipelines, :dast_profiles, column: :dast_profile_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :dast_profiles_pipelines, column: :dast_profile_id
end
end
end
# frozen_string_literal: true
class AddCiPipelineIdFkToDastProfilesPipelines < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :dast_profiles_pipelines, :ci_pipelines, column: :ci_pipeline_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :dast_profiles_pipelines, column: :ci_pipeline_id
end
end
end
6fb6381e969d062f19b5269b4958306c3bf9a1b7cf06e5b0eb25beb005952d07
\ No newline at end of file
d520fe71ca271c135b9684dc7a03ede27832659459f7476787798d11460c4736
\ No newline at end of file
fdf858a31e27fb2ce4071642b6e2d76082db95f6ebbec63ce627f92ddf7edfcf
\ No newline at end of file
......@@ -11850,6 +11850,13 @@ CREATE SEQUENCE dast_profiles_id_seq
ALTER SEQUENCE dast_profiles_id_seq OWNED BY dast_profiles.id;
CREATE TABLE dast_profiles_pipelines (
dast_profile_id bigint NOT NULL,
ci_pipeline_id bigint NOT NULL
);
COMMENT ON TABLE dast_profiles_pipelines IS '{"owner":"group::dynamic analysis","description":"Join table between DAST Profiles and CI Pipelines"}';
CREATE TABLE dast_scanner_profiles (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
......@@ -20535,6 +20542,9 @@ ALTER TABLE ONLY csv_issue_imports
ALTER TABLE ONLY custom_emoji
ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id);
ALTER TABLE ONLY dast_profiles_pipelines
ADD CONSTRAINT dast_profiles_pipelines_pkey PRIMARY KEY (dast_profile_id, ci_pipeline_id);
ALTER TABLE ONLY dast_profiles
ADD CONSTRAINT dast_profiles_pkey PRIMARY KEY (id);
......@@ -22470,6 +22480,8 @@ CREATE INDEX index_dast_profiles_on_dast_site_profile_id ON dast_profiles USING
CREATE UNIQUE INDEX index_dast_profiles_on_project_id_and_name ON dast_profiles USING btree (project_id, name);
CREATE UNIQUE INDEX index_dast_profiles_pipelines_on_ci_pipeline_id ON dast_profiles_pipelines USING btree (ci_pipeline_id);
CREATE UNIQUE INDEX index_dast_scanner_profiles_on_project_id_and_name ON dast_scanner_profiles USING btree (project_id, name);
CREATE INDEX index_dast_site_profiles_on_dast_site_id ON dast_site_profiles USING btree (dast_site_id);
......@@ -25141,6 +25153,9 @@ ALTER TABLE ONLY bulk_import_entities
ALTER TABLE ONLY users
ADD CONSTRAINT fk_a4b8fefe3e FOREIGN KEY (managing_group_id) REFERENCES namespaces(id) ON DELETE SET NULL;
ALTER TABLE ONLY dast_profiles_pipelines
ADD CONSTRAINT fk_a60cad829d FOREIGN KEY (ci_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_a6963e8447 FOREIGN KEY (target_project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -25246,6 +25261,9 @@ ALTER TABLE ONLY external_approval_rules_protected_branches
ALTER TABLE ONLY external_approval_rules_protected_branches
ADD CONSTRAINT fk_ca2ffb55e6 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_profiles_pipelines
ADD CONSTRAINT fk_cc206a8c13 FOREIGN KEY (dast_profile_id) REFERENCES dast_profiles(id) ON DELETE CASCADE;
ALTER TABLE ONLY experiment_subjects
ADD CONSTRAINT fk_ccc28f8ceb FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -58,11 +58,7 @@ module Mutations
::DastOnDemandScans::CreateService.new(
container: project,
current_user: current_user,
params: {
branch: dast_profile.branch_name,
dast_site_profile: dast_profile.dast_site_profile,
dast_scanner_profile: dast_profile.dast_scanner_profile
}
params: { dast_profile: dast_profile }
).execute
end
end
......
......@@ -10,6 +10,9 @@ module Dast
has_many :secret_variables, through: :dast_site_profile, class_name: 'Dast::SiteProfileSecretVariable'
has_many :dast_profiles_pipelines, class_name: 'Dast::ProfilesPipeline', foreign_key: :dast_profile_id, inverse_of: :dast_profile
has_many :ci_pipelines, class_name: 'Ci::Pipeline', through: :dast_profiles_pipelines
validates :description, length: { maximum: 255 }
validates :name, length: { maximum: 255 }, uniqueness: { scope: :project_id }, presence: true
validates :branch_name, length: { maximum: 255 }
......@@ -28,6 +31,10 @@ module Dast
Dast::Branch.new(self)
end
def ci_variables
::Gitlab::Ci::Variables::Collection.new(secret_variables)
end
private
def project_ids_match
......
# frozen_string_literal: true
module Dast
class ProfilesPipeline < ApplicationRecord
extend SuppressCompositePrimaryKeyWarning
self.table_name = 'dast_profiles_pipelines'
belongs_to :ci_pipeline, class_name: 'Ci::Pipeline', optional: false, inverse_of: :dast_profiles_pipeline
belongs_to :dast_profile, class_name: 'Dast::Profile', optional: false, inverse_of: :dast_profiles_pipelines
end
end
......@@ -43,6 +43,17 @@ module EE
end
end
override :variables
def variables
strong_memoize(:variables) do
super.tap do |collection|
if pipeline.triggered_for_ondemand_dast_scan? && pipeline.dast_profile
collection.concat(pipeline.dast_profile.ci_variables)
end
end
end
end
def shared_runners_minutes_limit_enabled?
project.shared_runners_minutes_limit_enabled? && runner&.minutes_cost_factor(project.visibility_level)&.positive?
end
......
......@@ -20,6 +20,8 @@ module EE
has_many :security_scans, class_name: 'Security::Scan', through: :builds
has_many :security_findings, class_name: 'Security::Finding', through: :security_scans, source: :findings
has_one :dast_profiles_pipeline, class_name: 'Dast::ProfilesPipeline', foreign_key: :ci_pipeline_id, inverse_of: :ci_pipeline
has_one :dast_profile, class_name: 'Dast::Profile', through: :dast_profiles_pipeline
has_one :source_project, class_name: 'Ci::Sources::Project', foreign_key: :pipeline_id
# Legacy way to fetch security reports based on job name. This has been replaced by the reports feature.
......@@ -166,6 +168,11 @@ module EE
security_findings.exists?
end
def triggered_for_ondemand_dast_scan?
ondemand_dast_scan? && parameter_source? &&
::Feature.enabled?(:security_dast_site_profiles_additional_fields, project, default_enabled: :yaml)
end
private
def has_security_reports?
......
......@@ -2,11 +2,14 @@
module Ci
class RunDastScanService < BaseService
def execute(branch:, **args)
def execute(branch:, dast_profile: nil, **args)
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
service = Ci::CreatePipelineService.new(project, current_user, ref: branch)
pipeline = service.execute(:ondemand_dast_scan, content: ci_yaml(args))
pipeline = service.execute(:ondemand_dast_scan, content: ci_yaml(args)) do |pipeline|
pipeline.dast_profile = dast_profile
end
if pipeline.created_successfully?
ServiceResponse.success(payload: pipeline)
......
......@@ -20,11 +20,7 @@ module Dast
response = ::DastOnDemandScans::CreateService.new(
container: container,
current_user: current_user,
params: {
branch: dast_profile.branch_name,
dast_site_profile: dast_site_profile,
dast_scanner_profile: dast_scanner_profile
}
params: { dast_profile: dast_profile }
).execute
return response if response.error?
......
......@@ -47,16 +47,10 @@ module Dast
end
def create_scan(dast_profile)
params = {
branch: dast_profile.branch_name,
dast_site_profile: dast_profile.dast_site_profile,
dast_scanner_profile: dast_profile.dast_scanner_profile
}
::DastOnDemandScans::CreateService.new(
container: container,
current_user: current_user,
params: params
params: { dast_profile: dast_profile }
).execute
end
end
......
......@@ -31,21 +31,27 @@ module DastOnDemandScans
end
end
def dast_site
strong_memoize(:dast_site) do
dast_site_profile&.dast_site
def dast_profile
strong_memoize(:dast_profile) do
params[:dast_profile]
end
end
def dast_site_profile
strong_memoize(:dast_site_profile) do
params[:dast_site_profile]
dast_profile&.dast_site_profile || params[:dast_site_profile]
end
end
def dast_scanner_profile
strong_memoize(:dast_scanner_profile) do
params[:dast_scanner_profile]
dast_profile&.dast_scanner_profile || params[:dast_scanner_profile]
end
end
def dast_site
strong_memoize(:dast_site) do
dast_site_profile&.dast_site
end
end
......@@ -57,6 +63,7 @@ module DastOnDemandScans
def default_config
{
dast_profile: dast_profile,
branch: branch,
target_url: dast_site&.url
}
......
# frozen_string_literal: true
FactoryBot.define do
factory :dast_profiles_pipeline, class: 'Dast::ProfilesPipeline' do
dast_profile
ci_pipeline { association :ci_pipeline, project: dast_profile.project}
end
end
......@@ -44,19 +44,10 @@ RSpec.describe Mutations::Dast::Profiles::Create do
context 'when run_after_create=true' do
let(:run_after_create) { true }
it 'returns the pipeline_url' do
actual_url = subject[:pipeline_url]
pipeline = Ci::Pipeline.find_by(
project: project,
sha: project.repository.commits('orphaned-branch', limit: 1)[0].id,
source: :ondemand_dast_scan,
config_source: :parameter_source
)
expected_url = Rails.application.routes.url_helpers.project_pipeline_url(
project,
pipeline
)
expect(actual_url).to eq(expected_url)
it_behaves_like 'it creates a DAST on-demand scan pipeline'
it_behaves_like 'it delegates scan creation to another service' do
let(:delegated_params) { hash_including(dast_profile: instance_of(Dast::Profile)) }
end
end
......
......@@ -48,21 +48,23 @@ RSpec.describe Mutations::Dast::Profiles::Run do
project.add_developer(user)
end
it_behaves_like 'it delegates scan creation to another service' do
let(:delegated_params) do
{ branch: dast_profile.branch_name, dast_site_profile: dast_profile.dast_site_profile, dast_scanner_profile: dast_profile.dast_scanner_profile }
it_behaves_like 'it creates a DAST on-demand scan pipeline' do
context 'when there is a dast_site_profile_secret_variable associated with the dast_profile' do
let_it_be(:dast_site_profile_secret_variable) { create(:dast_site_profile_secret_variable, dast_site_profile: dast_profile.dast_site_profile, raw_value: 'hello, world') }
it 'makes the variable available to the dast build' do
subject
dast_build = pipeline.builds.find_by!(name: 'dast')
variable = dast_build.variables.find { |var| var[:key] == dast_site_profile_secret_variable.key }
expect(Base64.strict_decode64(variable.value)).to include('hello, world')
end
end
end
it 'returns a pipeline_url containing the correct path' do
actual_url = subject[:pipeline_url]
pipeline = Ci::Pipeline.last
expected_url = Gitlab::Routing.url_helpers.project_pipeline_url(
project,
pipeline
)
expect(actual_url).to eq(expected_url)
it_behaves_like 'it delegates scan creation to another service' do
let(:delegated_params) { hash_including(dast_profile: dast_profile) }
end
context 'when the dast_profile does not exist' do
......
......@@ -5,11 +5,12 @@ require 'spec_helper'
RSpec.describe Mutations::Dast::Profiles::Update do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:dast_profile, reload: true) { create(:dast_profile, project: project, branch_name: 'audio') }
let(:dast_profile_gid) { dast_profile.to_global_id }
let(:run_after_update) { false }
let(:params) do
{
......@@ -18,7 +19,8 @@ RSpec.describe Mutations::Dast::Profiles::Update do
description: SecureRandom.hex,
branch_name: 'orphaned-branch',
dast_site_profile_id: global_id_of(create(:dast_site_profile, project: project)),
dast_scanner_profile_id: global_id_of(create(:dast_scanner_profile, project: project))
dast_scanner_profile_id: global_id_of(create(:dast_scanner_profile, project: project)),
run_after_update: run_after_update
}
end
......@@ -77,6 +79,16 @@ RSpec.describe Mutations::Dast::Profiles::Update do
end
end
context 'when run_after_update=true' do
let(:run_after_update) { true }
it_behaves_like 'it creates a DAST on-demand scan pipeline'
it_behaves_like 'it delegates scan creation to another service' do
let(:delegated_params) { hash_including(dast_profile: dast_profile) }
end
end
context 'when the feature flag dast_branch_selection is disabled' do
it 'does not set the branch_name' do
stub_feature_flags(dast_branch_selection: false)
......
......@@ -147,6 +147,49 @@ RSpec.describe Ci::Build do
expect(features_variable[:value]).to include('multiple_ldap_servers')
end
end
context 'when there is a dast_profile associated with the pipeline' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:dast_profile) { create(:dast_profile, project: project) }
let_it_be(:dast_site_profile_secret_variable) { create(:dast_site_profile_secret_variable, key: 'DAST_PASSWORD_BASE64', dast_site_profile: dast_profile.dast_site_profile) }
let(:pipeline) { create(:ci_pipeline, pipeline_params.merge!(project: project, dast_profile: dast_profile) ) }
let(:key) { dast_site_profile_secret_variable.key }
let(:value) { dast_site_profile_secret_variable.value }
shared_examples 'a pipeline with no dast on-demand variables' do
it 'does not include variables associated with the profile' do
keys = subject.to_runner_variables.map { |var| var[:key] }
expect(keys).not_to include(key)
end
end
it_behaves_like 'a pipeline with no dast on-demand variables' do
let(:pipeline_params) { { config_source: :parameter_source } }
end
it_behaves_like 'a pipeline with no dast on-demand variables' do
let(:pipeline_params) { { source: :ondemand_dast_scan } }
end
context 'when the dast on-demand pipeline is correctly configured' do
let(:pipeline_params) { { source: :ondemand_dast_scan, config_source: :parameter_source } }
it 'includes variables associated with the profile' do
expect(subject.to_runner_variables).to include(key: key, value: value, public: false, masked: true)
end
end
it_behaves_like 'a pipeline with no dast on-demand variables' do
let(:pipeline_params) { { source: :ondemand_dast_scan, config_source: :parameter_source } }
before do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
end
end
end
end
describe 'variable CI_HAS_OPEN_REQUIREMENTS' do
......
......@@ -12,11 +12,15 @@ RSpec.describe Ci::Pipeline do
create(:ci_empty_pipeline, status: :created, project: project)
end
describe 'associations' do
it { is_expected.to have_many(:security_scans).through(:builds).class_name('Security::Scan') }
it { is_expected.to have_many(:security_findings).through(:security_scans).class_name('Security::Finding').source(:findings) }
it { is_expected.to have_many(:downstream_bridges) }
it { is_expected.to have_many(:vulnerability_findings).through(:vulnerabilities_finding_pipelines).class_name('Vulnerabilities::Finding') }
it { is_expected.to have_many(:vulnerabilities_finding_pipelines).class_name('Vulnerabilities::FindingPipeline') }
it { is_expected.to have_one(:dast_profiles_pipeline).class_name('Dast::ProfilesPipeline').with_foreign_key(:ci_pipeline_id).inverse_of(:ci_pipeline) }
it { is_expected.to have_one(:dast_profile).class_name('Dast::Profile').through(:dast_profiles_pipeline) }
end
describe '.failure_reasons' do
it 'contains failure reasons about exceeded limits' do
......@@ -616,6 +620,37 @@ RSpec.describe Ci::Pipeline do
end
end
describe '#triggered_for_ondemand_dast_scan?' do
let(:pipeline_params) { { source: :ondemand_dast_scan, config_source: :parameter_source } }
let(:pipeline) { build(:ci_pipeline, pipeline_params) }
subject { pipeline.triggered_for_ondemand_dast_scan? }
context 'when the feature flag is enabled' do
it { is_expected.to be_truthy }
context 'when the pipeline only has the correct source' do
let(:pipeline_params) { { source: :ondemand_dast_scan } }
it { is_expected.to be_falsey }
end
context 'when the pipeline only has the correct config_source' do
let(:pipeline_params) { { config_source: :parameter_source } }
it { is_expected.to be_falsey }
end
end
context 'when the feature flag is disabled' do
before do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
end
it { is_expected.to be_falsey }
end
end
describe '#needs_touch?' do
subject { pipeline.needs_touch? }
......
......@@ -3,13 +3,17 @@
require 'spec_helper'
RSpec.describe Dast::Profile, type: :model do
subject { create(:dast_profile) }
let_it_be(:project) { create(:project) }
subject { create(:dast_profile, project: project) }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:dast_site_profile) }
it { is_expected.to belong_to(:dast_scanner_profile) }
it { is_expected.to have_many(:secret_variables).through(:dast_site_profile).class_name('Dast::SiteProfileSecretVariable') }
it { is_expected.to have_many(:dast_profiles_pipelines).class_name('Dast::ProfilesPipeline').with_foreign_key(:dast_profile_id).inverse_of(:dast_profile) }
it { is_expected.to have_many(:ci_pipelines).through(:dast_profiles_pipelines).class_name('Ci::Pipeline') }
end
describe 'validations' do
......@@ -24,7 +28,6 @@ RSpec.describe Dast::Profile, type: :model do
it { is_expected.to validate_presence_of(:name) }
context 'when the project_id and dast_site_profile.project_id do not match' do
let(:project) { create(:project) }
let(:dast_site_profile) { create(:dast_site_profile) }
subject { build(:dast_profile, project: project, dast_site_profile: dast_site_profile) }
......@@ -38,7 +41,6 @@ RSpec.describe Dast::Profile, type: :model do
end
context 'when the project_id and dast_scanner_profile.project_id do not match' do
let(:project) { create(:project) }
let(:dast_scanner_profile) { create(:dast_scanner_profile) }
subject { build(:dast_profile, project: project, dast_scanner_profile: dast_scanner_profile) }
......@@ -96,5 +98,21 @@ RSpec.describe Dast::Profile, type: :model do
end
end
end
describe '#ci_variables' do
context 'when there are no secret_variables' do
it 'returns an empty collection' do
expect(subject.ci_variables.size).to be_zero
end
end
context 'when there are secret_variables' do
it 'returns a collection containing that variable' do
variable = create(:dast_site_profile_secret_variable, dast_site_profile: subject.dast_site_profile)
expect(subject.ci_variables.to_runner_variables).to include(key: variable.key, value: variable.value, public: false, masked: true)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Dast::Profile, type: :model do
subject { create(:dast_profiles_pipeline) }
describe 'associations' do
it { is_expected.to belong_to(:ci_pipeline).class_name('Ci::Pipeline').inverse_of(:dast_profiles_pipeline).required }
it { is_expected.to belong_to(:dast_profile).class_name('Dast::Profile').inverse_of(:dast_profiles_pipelines).required }
end
end
......@@ -3,17 +3,18 @@
require 'spec_helper'
RSpec.describe Ci::RunDastScanService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user) }
let(:branch) { project.default_branch }
let(:spider_timeout) { 42 }
let(:target_timeout) { 21 }
let(:target_url) { generate(:url) }
let(:use_ajax_spider) { true }
let(:show_debug_messages) { false }
let(:full_scan_enabled) { true }
let(:excluded_urls) { "#{target_url}/hello,#{target_url}/world" }
let(:auth_url) { "#{target_url}/login" }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user) }
let_it_be(:dast_profile) { create(:dast_profile) }
let_it_be(:branch) { project.default_branch }
let_it_be(:spider_timeout) { 42 }
let_it_be(:target_timeout) { 21 }
let_it_be(:target_url) { generate(:url) }
let_it_be(:use_ajax_spider) { true }
let_it_be(:show_debug_messages) { false }
let_it_be(:full_scan_enabled) { true }
let_it_be(:excluded_urls) { "#{target_url}/hello,#{target_url}/world" }
let_it_be(:auth_url) { "#{target_url}/login" }
before do
stub_licensed_features(security_on_demand_scans: true)
......@@ -33,7 +34,8 @@ RSpec.describe Ci::RunDastScanService do
auth_url: auth_url,
auth_username_field: 'session[username]',
auth_password_field: 'session[password]',
auth_username: 'tanuki'
auth_username: 'tanuki',
dast_profile: dast_profile
)
end
......@@ -173,6 +175,26 @@ RSpec.describe Ci::RunDastScanService do
expect(build.yaml_variables).to contain_exactly(*expected_variables)
end
it 'associates the dast_profile with the pipeline' do
expect(pipeline.dast_profile).to eq(dast_profile)
end
context 'when no dast_profile is provided' do
let(:dast_profile) { nil }
it 'does not create a new association' do
expect(pipeline.dast_profile).to be_nil
end
end
context 'when creating the association betweeen the dast_profile and the pipeline fails' do
let_it_be(:dast_profile) { build(:dast_site_profile) }
it 'does not create a Ci::Pipeline' do
expect { subject }.to raise_error(ActiveRecord::AssociationTypeMismatch).and change { Ci::Pipeline.count }.by(0)
end
end
context 'when the pipeline fails to save' do
before do
allow_any_instance_of(Ci::Pipeline).to receive(:created_successfully?).and_return(false)
......
......@@ -52,9 +52,7 @@ RSpec.describe Dast::Profiles::CreateService do
let(:params) { default_params.merge(run_after_create: true) }
it_behaves_like 'it delegates scan creation to another service' do
let(:delegated_params) do
{ branch: default_params[:branch_name], dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile }
end
let(:delegated_params) { hash_including(dast_profile: instance_of(Dast::Profile)) }
end
it 'creates a ci_pipeline' do
......
......@@ -80,9 +80,7 @@ RSpec.describe Dast::Profiles::UpdateService do
let(:params) { default_params.merge(run_after_update: true) }
it_behaves_like 'it delegates scan creation to another service' do
let(:delegated_params) do
{ branch: dast_profile.branch_name, dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile }
end
let(:delegated_params) { hash_including(dast_profile: dast_profile) }
end
it 'creates a ci_pipeline' do
......
......@@ -18,6 +18,18 @@ RSpec.describe DastOnDemandScans::CreateService do
).execute
end
shared_examples 'a service that calls Ci::RunDastScanService' do
it 'delegates pipeline creation to Ci::RunDastScanService', :aggregate_failures do
service = double(Ci::RunDastScanService)
response = ServiceResponse.error(message: 'Stubbed response')
expect(Ci::RunDastScanService).to receive(:new).and_return(service)
expect(service).to receive(:execute).with(expected_params).and_return(response)
subject
end
end
describe 'execute' do
context 'when on demand scan licensed feature is not available' do
context 'when the user cannot run an on demand scan' do
......@@ -49,12 +61,14 @@ RSpec.describe DastOnDemandScans::CreateService do
expect(subject.payload[:pipeline_url]).to be_a(String)
end
it 'delegates pipeline creation to Ci::RunDastScanService', :aggregate_failures do
expected_params = {
it_behaves_like 'a service that calls Ci::RunDastScanService' do
let(:expected_params) do
{
auth_password_field: dast_site_profile.auth_password_field,
auth_username: dast_site_profile.auth_username,
auth_username_field: dast_site_profile.auth_username_field,
branch: project.default_branch_or_master,
dast_profile: nil,
excluded_urls: dast_site_profile.excluded_urls.join(','),
full_scan_enabled: false,
show_debug_messages: false,
......@@ -63,14 +77,7 @@ RSpec.describe DastOnDemandScans::CreateService do
target_url: dast_site_profile.dast_site.url,
use_ajax_spider: false
}
service = double(Ci::RunDastScanService)
response = ServiceResponse.error(message: 'Stubbed response')
expect(Ci::RunDastScanService).to receive(:new).and_return(service)
expect(service).to receive(:execute).with(expected_params).and_return(response)
subject
end
end
context 'when a branch is specified' do
......@@ -100,6 +107,20 @@ RSpec.describe DastOnDemandScans::CreateService do
end
end
context 'when dast_profile is specified' do
let_it_be(:dast_profile) { create(:dast_profile, project: project) }
let(:params) { { dast_profile: dast_profile } }
it 'communicates success' do
expect(subject.status).to eq(:success)
end
it_behaves_like 'a service that calls Ci::RunDastScanService' do
let(:expected_params) { hash_including(dast_profile: dast_profile) }
end
end
context 'when target is not validated and an active scan is requested' do
let(:dast_scanner_profile) { create(:dast_scanner_profile, project: project, scan_type: 'active') }
......
......@@ -39,12 +39,13 @@ RSpec.describe DastOnDemandScans::ParamsCreateService do
it 'returns prepared scanner params in the payload' do
expect(subject.payload).to eq(
auth_password_field: dast_site_profile.auth_password_field,
auth_username: dast_site_profile.auth_username,
auth_username_field: dast_site_profile.auth_username_field,
branch: project.default_branch,
target_url: dast_site_profile.dast_site.url,
dast_profile: nil,
excluded_urls: dast_site_profile.excluded_urls.join(','),
auth_username_field: dast_site_profile.auth_username_field,
auth_password_field: dast_site_profile.auth_password_field,
auth_username: dast_site_profile.auth_username
target_url: dast_site_profile.dast_site.url
)
end
end
......@@ -54,16 +55,17 @@ RSpec.describe DastOnDemandScans::ParamsCreateService do
it 'returns prepared scanner params in the payload' do
expect(subject.payload).to eq(
branch: project.default_branch,
target_url: dast_site_profile.dast_site.url,
excluded_urls: dast_site_profile.excluded_urls.join(','),
auth_username_field: dast_site_profile.auth_username_field,
auth_password_field: dast_site_profile.auth_password_field,
auth_username: dast_site_profile.auth_username,
auth_username_field: dast_site_profile.auth_username_field,
branch: project.default_branch,
dast_profile: nil,
excluded_urls: dast_site_profile.excluded_urls.join(','),
full_scan_enabled: false,
show_debug_messages: false,
spider_timeout: nil,
target_timeout: nil,
target_url: dast_site_profile.dast_site.url,
use_ajax_spider: false
)
end
......@@ -80,5 +82,28 @@ RSpec.describe DastOnDemandScans::ParamsCreateService do
end
end
end
context 'when the dast_profile is provided' do
let_it_be(:dast_profile) { create(:dast_profile, project: project, dast_site_profile: dast_site_profile, dast_scanner_profile: dast_scanner_profile) }
let(:params) { { dast_profile: dast_profile } }
it 'returns prepared scanner params in the payload' do
expect(subject.payload).to eq(
auth_password_field: dast_site_profile.auth_password_field,
auth_username: dast_site_profile.auth_username,
auth_username_field: dast_site_profile.auth_username_field,
branch: project.default_branch,
dast_profile: dast_profile,
excluded_urls: dast_site_profile.excluded_urls.join(','),
full_scan_enabled: false,
show_debug_messages: false,
spider_timeout: nil,
target_timeout: nil,
target_url: dast_site_profile.dast_site.url,
use_ajax_spider: false
)
end
end
end
end
......@@ -53,6 +53,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::OnDemandScanPipelineConf
auth_password_field: site_profile.auth_password_field,
auth_username: site_profile.auth_username,
auth_username_field: site_profile.auth_username_field,
dast_profile: nil,
branch: project.default_branch_or_master,
excluded_urls: site_profile.excluded_urls.join(','),
full_scan_enabled: false,
......
# frozen_string_literal: true
require 'spec_helper'
# There must be a methods or lets called `project` and `dast_profile` defined.
RSpec.shared_examples 'it creates a DAST on-demand scan pipeline' do
let(:pipeline) do
Ci::Pipeline.find_by!(
project: project,
sha: project.repository.commit.sha,
source: :ondemand_dast_scan,
config_source: :parameter_source
)
end
it 'creates a new ci_pipeline for the given project', :aggregate_failures do
expect { subject }.to change { Ci::Pipeline.where(project: project).count }.by(1)
expect(pipeline.triggered_for_ondemand_dast_scan?).to be_truthy
end
it 'creates a single build associated with the ci_pipeline' do
subject
expect(pipeline.builds.map(&:name)).to eq(['dast'])
end
it 'creates an association between the dast_profile and the ci_pipeline' do
subject
expect(dast_profile.ci_pipelines).to include(pipeline)
end
it 'returns the pipeline_url' do
subject
expected_url = Rails.application.routes.url_helpers.project_pipeline_url(
project,
pipeline
)
expect(subject[:pipeline_url]).to eq(expected_url)
end
end
......@@ -256,6 +256,8 @@ ci_pipelines:
- messages
- pipeline_artifacts
- latest_statuses
- dast_profile
- dast_profiles_pipeline
ci_refs:
- project
- ci_pipelines
......
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