Commit bb54da0d authored by Philip Cunningham's avatar Philip Cunningham Committed by Ash McKenzie

Add ability to create DastSiteProfile with GraphQL

Hooks models into services to create records in the db.
parent 4eeb6114
...@@ -30,12 +30,12 @@ module Mutations ...@@ -30,12 +30,12 @@ module Mutations
raise_resource_not_available_error! unless Feature.enabled?(:security_on_demand_scans_feature_flag, project) raise_resource_not_available_error! unless Feature.enabled?(:security_on_demand_scans_feature_flag, project)
service = ::DastSiteProfiles::CreateService.new(project, current_user) service = ::DastSiteProfiles::CreateService.new(project, current_user)
dast_site_profile = service.execute(name: profile_name, target_url: target_url) result = service.execute(name: profile_name, target_url: target_url)
if dast_site_profile.success? if result.success?
raise 'Not implemented' { id: result.payload.to_global_id, errors: [] }
else else
{ errors: dast_site_profile.errors } { errors: result.errors }
end end
end end
......
...@@ -2,12 +2,23 @@ ...@@ -2,12 +2,23 @@
module DastSiteProfiles module DastSiteProfiles
class CreateService < BaseService class CreateService < BaseService
def execute(name: nil, target_url: nil) def execute(name:, target_url:)
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed? return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
ServiceResponse.error(message: 'Not implemented') ActiveRecord::Base.transaction do
service = DastSites::FindOrCreateService.new(project, current_user)
dast_site = service.execute!(url: target_url)
dast_site_profile = DastSiteProfile.create!(project: project, dast_site: dast_site, name: name)
ServiceResponse.success(payload: dast_site_profile)
end
rescue ActiveRecord::RecordInvalid => err
ServiceResponse.error(message: err.record.errors.full_messages)
end end
private
def allowed? def allowed?
Ability.allowed?(current_user, :run_ondemand_dast_scan, project) Ability.allowed?(current_user, :run_ondemand_dast_scan, project)
end end
......
# frozen_string_literal: true
module DastSites
class FindOrCreateService < BaseService
PermissionsError = Class.new(StandardError)
def execute!(url:)
raise PermissionsError.new('Insufficient permissions') unless allowed?
find_or_create_by!(url)
end
private
def allowed?
Ability.allowed?(current_user, :run_ondemand_dast_scan, project)
end
def find_or_create_by!(url)
DastSite.safe_find_or_create_by!(project: project, url: url)
end
end
end
...@@ -9,6 +9,7 @@ RSpec.describe Mutations::DastSiteProfiles::Create do ...@@ -9,6 +9,7 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
let(:full_path) { project.full_path } let(:full_path) { project.full_path }
let(:profile_name) { SecureRandom.hex } let(:profile_name) { SecureRandom.hex }
let(:target_url) { FFaker::Internet.uri(:https) } let(:target_url) { FFaker::Internet.uri(:https) }
let(:dast_site_profile) { DastSiteProfile.find_by(project: project, name: profile_name) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
...@@ -47,26 +48,52 @@ RSpec.describe Mutations::DastSiteProfiles::Create do ...@@ -47,26 +48,52 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
end end
context 'when the user is an owner' do context 'when the user is an owner' do
it 'stubs out the response' do it 'returns the dast_site_profile id' do
group.add_owner(user) group.add_owner(user)
expect(subject[:errors]).to eq(['Not implemented']) expect(subject[:id]).to eq(dast_site_profile.to_global_id)
end end
end end
context 'when the user is a maintainer' do context 'when the user is a maintainer' do
it 'stubs out the response' do it 'returns the dast_site_profile id' do
project.add_maintainer(user) project.add_maintainer(user)
expect(subject[:errors]).to eq(['Not implemented']) expect(subject[:id]).to eq(dast_site_profile.to_global_id)
end end
end end
context 'when the user is a developer' do context 'when the user is a developer' do
it 'stubs out the response' do before do
project.add_developer(user) project.add_developer(user)
end
it 'returns the dast_site_profile id' do
expect(subject[:id]).to eq(dast_site_profile.to_global_id)
end
it 'calls the dast_site_profile creation service' do
service = double(described_class)
result = double('result', success?: false, errors: [])
expect(DastSiteProfiles::CreateService).to receive(:new).and_return(service)
expect(service).to receive(:execute).with(name: profile_name, target_url: target_url).and_return(result)
subject
end
context 'when the project name already exists' do
it 'returns an error' do
subject
response = mutation.resolve(
full_path: full_path,
profile_name: profile_name,
target_url: target_url
)
expect(subject[:errors]).to eq(['Not implemented']) expect(response[:errors]).to include('Name has already been taken')
end
end end
end end
end end
......
...@@ -10,6 +10,7 @@ RSpec.describe 'Creating a DAST Site Profile' do ...@@ -10,6 +10,7 @@ RSpec.describe 'Creating a DAST Site Profile' do
let(:full_path) { project.full_path } let(:full_path) { project.full_path }
let(:profile_name) { FFaker::Company.catch_phrase } let(:profile_name) { FFaker::Company.catch_phrase }
let(:target_url) { FFaker::Internet.uri(:https) } let(:target_url) { FFaker::Internet.uri(:https) }
let(:dast_site_profile) { DastSiteProfile.find_by(project: project, name: profile_name) }
let(:mutation) do let(:mutation) do
graphql_mutation( graphql_mutation(
...@@ -46,7 +47,19 @@ RSpec.describe 'Creating a DAST Site Profile' do ...@@ -46,7 +47,19 @@ RSpec.describe 'Creating a DAST Site Profile' do
project.add_developer(current_user) project.add_developer(current_user)
end end
it_behaves_like 'a mutation that returns errors in the response', errors: ['Not implemented'] it 'returns the dast_site_profile id' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response["id"]).to eq(dast_site_profile.to_global_id.to_s)
end
context 'when an unknown error occurs' do
before do
allow(DastSiteProfile).to receive(:create!).and_raise(StandardError)
end
it_behaves_like 'a mutation that returns top-level errors', errors: ['Internal server error']
end
end end
end end
end end
...@@ -13,6 +13,8 @@ RSpec.describe DastSiteProfiles::CreateService do ...@@ -13,6 +13,8 @@ RSpec.describe DastSiteProfiles::CreateService do
let(:status) { subject.status } let(:status) { subject.status }
let(:message) { subject.message } let(:message) { subject.message }
let(:errors) { subject.errors }
let(:payload) { subject.payload }
context 'when the user does not have permission to run a dast scan' do context 'when the user does not have permission to run a dast scan' do
it 'returns an error status' do it 'returns an error status' do
...@@ -29,12 +31,46 @@ RSpec.describe DastSiteProfiles::CreateService do ...@@ -29,12 +31,46 @@ RSpec.describe DastSiteProfiles::CreateService do
project.add_developer(user) project.add_developer(user)
end end
it 'returns an error status' do it 'returns a success status' do
expect(status).to eq(:error) expect(status).to eq(:success)
end end
it 'populates message' do it 'creates a dast_site_profile' do
expect(message).to eq('Not implemented') expect { subject }.to change(DastSiteProfile, :count).by(1)
end
it 'creates a dast_site' do
expect { subject }.to change(DastSite, :count).by(1)
end
it 'returns a dast_site_profile payload' do
expect(payload).to be_a(DastSiteProfile)
end
context 'when the dast_site already exists' do
before do
create(:dast_site, project: project, url: target_url)
end
it 'returns a success status' do
expect(status).to eq(:success)
end
it 'does not create a new dast_site' do
expect { subject }.not_to change(DastSite, :count)
end
end
context 'when the target url is localhost' do
let(:target_url) { 'http://localhost:3000/hello-world' }
it 'returns an error status' do
expect(status).to eq(:error)
end
it 'populates errors' do
expect(errors).to include('Url is blocked: Requests to localhost are not allowed')
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DastSites::FindOrCreateService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user) }
let(:url) { FFaker::Internet.uri(:http) }
describe '#execute!' do
subject { described_class.new(project, user).execute!(url: url) }
context 'when the user does not have permission to run a dast scan' do
it 'raises an exception' do
expect { subject }.to raise_error(DastSites::FindOrCreateService::PermissionsError) do |err|
expect(err.message).to include('Insufficient permissions')
end
end
end
context 'when the user can run a dast scan' do
before do
project.add_developer(user)
end
it 'returns a dast_site' do
expect(subject).to be_a(DastSite)
end
it 'creates a dast_site' do
expect { subject }.to change(DastSite, :count).by(1)
end
context 'when the dast_site already exists' do
before do
create(:dast_site, project: project, url: url)
end
it 'returns the existing dast_site' do
expect(subject).to be_a(DastSite)
end
it 'does not create a new dast_site' do
expect { subject }.not_to change(DastSite, :count)
end
end
context 'when the target url is localhost' do
let(:url) { 'http://localhost:3000/hello-world' }
it 'raises an exception' do
expect { subject }.to raise_error(ActiveRecord::RecordInvalid) do |err|
expect(err.record.errors.full_messages).to include('Url is blocked: Requests to localhost are not allowed')
end
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment