Commit 9a81daba authored by Philip Cunningham's avatar Philip Cunningham Committed by Sean McGivern

Add GraphQL mutation to delete DastSiteProfile

Adds new mutation that enables us to delete DastSiteProfiles by their
global ID.
parent a2a272b0
......@@ -2359,6 +2359,46 @@ type DastSiteProfileCreatePayload {
id: ID
}
"""
Autogenerated input type of DastSiteProfileDelete
"""
input DastSiteProfileDeleteInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The project the site profile belongs to.
"""
fullPath: ID!
"""
ID of the site profile to be deleted.
"""
id: DastSiteProfileID!
}
"""
Autogenerated return type of DastSiteProfileDelete
"""
type DastSiteProfileDeletePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
"""
Identifier of DastSiteProfile
"""
scalar DastSiteProfileID
"""
Autogenerated input type of DeleteAnnotation
"""
......@@ -8496,6 +8536,7 @@ type Mutation {
dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload
dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload
dastSiteProfileCreate(input: DastSiteProfileCreateInput!): DastSiteProfileCreatePayload
dastSiteProfileDelete(input: DastSiteProfileDeleteInput!): DastSiteProfileDeletePayload
deleteAnnotation(input: DeleteAnnotationInput!): DeleteAnnotationPayload
designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
......
......@@ -6340,6 +6340,118 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DastSiteProfileDeleteInput",
"description": "Autogenerated input type of DastSiteProfileDelete",
"fields": null,
"inputFields": [
{
"name": "fullPath",
"description": "The project the site profile belongs to.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "id",
"description": "ID of the site profile to be deleted.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastSiteProfileID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastSiteProfileDeletePayload",
"description": "Autogenerated return type of DastSiteProfileDelete",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "DastSiteProfileID",
"description": "Identifier of DastSiteProfile",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DeleteAnnotationInput",
......@@ -24452,6 +24564,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteProfileDelete",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "DastSiteProfileDeleteInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastSiteProfileDeletePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "deleteAnnotation",
"description": null,
......@@ -412,6 +412,15 @@ Autogenerated return type of DastSiteProfileCreate
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `id` | ID | ID of the site profile. |
## DastSiteProfileDeletePayload
Autogenerated return type of DastSiteProfileDelete
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
## DeleteAnnotationPayload
Autogenerated return type of DeleteAnnotation
......
......@@ -24,6 +24,7 @@ module EE
mount_mutation ::Mutations::Pipelines::RunDastScan
mount_mutation ::Mutations::DastOnDemandScans::Create
mount_mutation ::Mutations::DastSiteProfiles::Create
mount_mutation ::Mutations::DastSiteProfiles::Delete
mount_mutation ::Mutations::DastScannerProfiles::Create
mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily
end
......
# frozen_string_literal: true
module Mutations
module DastSiteProfiles
class Delete < BaseMutation
include ResolvesProject
graphql_name 'DastSiteProfileDelete'
argument :full_path, GraphQL::ID_TYPE,
required: true,
description: 'The project the site profile belongs to.'
argument :id, ::Types::GlobalIDType[::DastSiteProfile],
required: true,
description: 'ID of the site profile to be deleted.'
authorize :run_ondemand_dast_scan
def resolve(full_path:, id:)
project = authorized_find!(full_path: full_path)
raise_resource_not_available_error! unless Feature.enabled?(:security_on_demand_scans_feature_flag, project)
dast_site_profile = find_dast_site_profile(project: project, global_id: id)
return { errors: dast_site_profile.errors.full_messages } unless dast_site_profile.destroy
{ errors: [] }
end
private
def find_object(full_path:)
resolve_project(full_path: full_path)
end
def find_dast_site_profile(project:, global_id:)
project.dast_site_profiles.find(global_id.model_id)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::DastSiteProfiles::Delete do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:user) { create(:user) }
let(:full_path) { project.full_path }
let!(:dast_site_profile) { create(:dast_site_profile, project: project) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
describe '#resolve' do
subject do
mutation.resolve(
full_path: full_path,
id: dast_site_profile.to_global_id
)
end
context 'when on demand scan feature is enabled' do
context 'when the project does not exist' do
let(:full_path) { SecureRandom.hex }
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'has no errors' do
group.add_owner(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a maintainer' do
it 'has no errors' do
project.add_maintainer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a developer' do
it 'has no errors' do
project.add_developer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a reporter' do
it 'raises an exception' do
project.add_reporter(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is a guest' do
it 'raises an exception' do
project.add_guest(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a dast scan' do
before do
project.add_developer(user)
end
it 'deletes the dast_site_profile' do
expect { subject }.to change { DastSiteProfile.count }.by(-1)
end
context 'when there is an issue deleting the dast_site_profile' do
it 'returns an error' do
allow(mutation).to receive(:find_dast_site_profile).and_return(dast_site_profile)
allow(dast_site_profile).to receive(:destroy).and_return(false)
dast_site_profile.errors.add(:name, 'is weird')
expect(subject[:errors]).to include('Name is weird')
end
end
context 'when on demand scan feature is not enabled' do
it 'raises an exception' do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Creating a DAST Site Profile' do
include GraphqlHelpers
let(:project) { create(:project) }
let(:current_user) { create(:user) }
let(:full_path) { project.full_path }
let!(:dast_site_profile) { create(:dast_site_profile, project: project) }
let(:mutation) do
graphql_mutation(
:dast_site_profile_delete,
full_path: full_path,
id: dast_site_profile.to_global_id.to_s
)
end
def mutation_response
graphql_mutation_response(:dast_site_profile_delete)
end
subject { post_graphql_mutation(mutation, current_user: current_user) }
context 'when a user does not have access to the project' do
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
end
context 'when a user does not have access to run a dast scan on the project' do
before do
project.add_guest(current_user)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
end
context 'when a user has access to run a dast scan on the project' do
before do
project.add_developer(current_user)
end
it 'returns an empty errors array' do
subject
expect(mutation_response["errors"]).to be_empty
end
it 'deletes the dast_site_profile' do
expect { subject }.to change { DastSiteProfile.count }.by(-1)
end
context 'when there is an issue deleting the dast_site_profile' do
before do
mutation_klass = Mutations::DastSiteProfiles::Delete
allow_any_instance_of(mutation_klass).to receive(:find_dast_site_profile).and_return(dast_site_profile)
allow(dast_site_profile).to receive(:destroy).and_return(false)
dast_site_profile.errors.add(:name, 'is weird')
end
it_behaves_like 'a mutation that returns errors in the response', errors: ['Name is weird']
end
context 'when the dast_site_profile does not exist' do
before do
dast_site_profile.destroy!
end
it_behaves_like 'a mutation that returns top-level errors', errors: ['Internal server error']
end
context 'when wrong type of global id is passed' do
let(:mutation) do
graphql_mutation(
:dast_site_profile_delete,
full_path: full_path,
id: dast_site_profile.dast_site.to_global_id.to_s
)
end
it 'returns a top-level error' do
subject
expect(graphql_errors.dig(0, 'message')).to include('does not represent an instance of DastSiteProfile')
end
end
context 'when the dast_site_profile belongs to a different project' do
let(:mutation) do
graphql_mutation(
:dast_site_profile_delete,
full_path: create(:project).full_path,
id: dast_site_profile.to_global_id.to_s
)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
end
end
context 'when on demand scan feature is disabled' do
before do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not ' \
'exist or you don\'t have permission to perform this action']
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