Commit e2fad778 authored by Philip Cunningham's avatar Philip Cunningham Committed by Mayra Cabrera

Add dastSiteValidationRevoke GraphQL mutation

Add ability to revoke ability to run active scans.
parent 5974b19a
......@@ -6175,6 +6175,41 @@ Identifier of DastSiteValidation
"""
scalar DastSiteValidationID
"""
Autogenerated input type of DastSiteValidationRevoke
"""
input DastSiteValidationRevokeInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The project the site validation belongs to.
"""
fullPath: ID!
"""
Normalized URL of the target to be revoked.
"""
normalizedTargetUrl: String!
}
"""
Autogenerated return type of DastSiteValidationRevoke
"""
type DastSiteValidationRevokePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
enum DastSiteValidationStrategyEnum {
"""
Header validation
......@@ -15623,6 +15658,7 @@ type Mutation {
dastSiteProfileUpdate(input: DastSiteProfileUpdateInput!): DastSiteProfileUpdatePayload
dastSiteTokenCreate(input: DastSiteTokenCreateInput!): DastSiteTokenCreatePayload
dastSiteValidationCreate(input: DastSiteValidationCreateInput!): DastSiteValidationCreatePayload
dastSiteValidationRevoke(input: DastSiteValidationRevokeInput!): DastSiteValidationRevokePayload
deleteAnnotation(input: DeleteAnnotationInput!): DeleteAnnotationPayload
deleteDevopsAdoptionSegment(input: DeleteDevopsAdoptionSegmentInput!): DeleteDevopsAdoptionSegmentPayload
designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload
......
......@@ -16914,6 +16914,108 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DastSiteValidationRevokeInput",
"description": "Autogenerated input type of DastSiteValidationRevoke",
"fields": null,
"inputFields": [
{
"name": "fullPath",
"description": "The project the site validation belongs to.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "normalizedTargetUrl",
"description": "Normalized URL of the target to be revoked.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"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": "DastSiteValidationRevokePayload",
"description": "Autogenerated return type of DastSiteValidationRevoke",
"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": "ENUM",
"name": "DastSiteValidationStrategyEnum",
......@@ -44005,6 +44107,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteValidationRevoke",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "DastSiteValidationRevokeInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastSiteValidationRevokePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "deleteAnnotation",
"description": null,
......@@ -998,6 +998,15 @@ Autogenerated return type of DastSiteValidationCreate.
| `id` | DastSiteValidationID | ID of the site validation. |
| `status` | DastSiteProfileValidationStatusEnum | The current validation status. |
### DastSiteValidationRevokePayload
Autogenerated return type of DastSiteValidationRevoke.
| Field | 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.
......
......@@ -43,6 +43,7 @@ module EE
mount_mutation ::Mutations::DastSiteProfiles::Update
mount_mutation ::Mutations::DastSiteProfiles::Delete
mount_mutation ::Mutations::DastSiteValidations::Create
mount_mutation ::Mutations::DastSiteValidations::Revoke
mount_mutation ::Mutations::DastScannerProfiles::Create
mount_mutation ::Mutations::DastScannerProfiles::Update
mount_mutation ::Mutations::DastScannerProfiles::Delete
......
# frozen_string_literal: true
module Mutations
module DastSiteValidations
class Revoke < BaseMutation
FEATURE_FLAG = :security_on_demand_scans_site_validation
include FindsProject
graphql_name 'DastSiteValidationRevoke'
argument :full_path, GraphQL::ID_TYPE,
required: true,
description: 'The project the site validation belongs to.'
argument :normalized_target_url, GraphQL::STRING_TYPE,
required: true,
description: 'Normalized URL of the target to be revoked.'
authorize :create_on_demand_dast_scan
def resolve(full_path:, normalized_target_url:)
project = authorized_find!(full_path)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Feature disabled: #{FEATURE_FLAG}" unless allowed?(project)
response = ::DastSiteValidations::RevokeService.new(
container: project,
params: { url_base: normalized_target_url }
).execute
return error_response(response.errors) if response.error?
success_response
end
private
def allowed?(project)
Feature.enabled?(FEATURE_FLAG, project, default_enabled: :yaml)
end
def error_response(errors)
{ errors: errors }
end
def success_response
{ errors: [] }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::DastSiteValidations::Revoke do
let_it_be(:dast_site_validation1) { create(:dast_site_validation, state: :passed)}
let_it_be(:dast_site_validation2) { create(:dast_site_validation)}
let_it_be(:project) { dast_site_validation1.project }
let_it_be(:user) { create(:user) }
let(:full_path) { project.full_path }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
before do
stub_licensed_features(security_on_demand_scans: true)
end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do
subject do
mutation.resolve(
full_path: full_path,
normalized_target_url: dast_site_validation1.url_base
)
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 can run a dast scan' do
before do
project.add_developer(user)
end
it 'deletes dast_site_validations where state=passed' do
aggregate_failures do
expect { subject }.to change { DastSiteValidation.count }.from(2).to(1)
expect { dast_site_validation1.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
it 'correctly calls DastSiteValidations::RevokeService' do
params = { container: project, params: { url_base: dast_site_validation1.url_base } }
expect(DastSiteValidations::RevokeService).to receive(:new).with(params).and_call_original
subject
end
context 'when on demand scan site validations feature is not enabled' do
it 'raises an exception' do
stub_feature_flags(security_on_demand_scans_site_validation: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) do |err|
expect(err.message).to eq('Feature disabled: security_on_demand_scans_site_validation')
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Creating a DAST Site Token' do
include GraphqlHelpers
let(:dast_site_token) { create(:dast_site_token, project: project)}
let!(:dast_site_validation) { create(:dast_site_validation, state: :passed, dast_site_token: dast_site_token)}
let(:mutation_name) { :dast_site_validation_revoke }
let(:mutation) do
graphql_mutation(
mutation_name,
full_path: project.full_path,
normalized_target_url: dast_site_validation.url_base
)
end
it_behaves_like 'an on-demand scan mutation when user cannot run an on-demand scan'
it_behaves_like 'an on-demand scan mutation when user can run an on-demand scan' do
it 'deletes dast_site_validations where state=passed' do
expect { subject }.to change { DastSiteValidation.count }.from(1).to(0)
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