Commit 4ba72a64 authored by Craig Smith's avatar Craig Smith

Add DAST Site Validation GraphQL query endpoint

This commit adds the DAST Site
Validation GraphQL query endpoint to enable
frontend to get the status of a DAST Site
Validation
parent ca70733e
......@@ -4579,6 +4579,21 @@ Identifier of DastSiteToken
"""
scalar DastSiteTokenID
"""
Represents a DAST Site Validation
"""
type DastSiteValidation {
"""
ID of the site validation
"""
id: DastSiteValidationID!
"""
The status of the validation
"""
status: DastSiteProfileValidationStatusEnum!
}
"""
Autogenerated input type of DastSiteValidationCreate
"""
......@@ -13906,6 +13921,16 @@ type Project {
last: Int
): DastSiteProfileConnection
"""
DAST Site Validation associated with the project
"""
dastSiteValidation(
"""
target URL of the DAST Site Validation
"""
targetUrl: String!
): DastSiteValidation
"""
Short description of the project
"""
......
......@@ -12396,6 +12396,55 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastSiteValidation",
"description": "Represents a DAST Site Validation",
"fields": [
{
"name": "id",
"description": "ID of the site validation",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastSiteValidationID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "status",
"description": "The status of the validation",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "DastSiteProfileValidationStatusEnum",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DastSiteValidationCreateInput",
......@@ -40716,6 +40765,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteValidation",
"description": "DAST Site Validation associated with the project",
"args": [
{
"name": "targetUrl",
"description": "target URL of the DAST Site Validation",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastSiteValidation",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": "Short description of the project",
......@@ -733,6 +733,15 @@ Autogenerated return type of DastSiteTokenCreate.
| `status` | DastSiteProfileValidationStatusEnum | The current validation status of the target. |
| `token` | String | Token string. |
### DastSiteValidation
Represents a DAST Site Validation.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `id` | DastSiteValidationID! | ID of the site validation |
| `status` | DastSiteProfileValidationStatusEnum! | The status of the validation |
### DastSiteValidationCreatePayload
Autogenerated return type of DastSiteValidationCreate.
......@@ -1979,6 +1988,7 @@ Autogenerated return type of PipelineRetry.
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
| `createdAt` | Time | Timestamp of the project creation |
| `dastSiteProfile` | DastSiteProfile | DAST Site Profile associated with the project |
| `dastSiteValidation` | DastSiteValidation | DAST Site Validation associated with the project |
| `description` | String | Short description of the project |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `environment` | Environment | A single environment of the project |
......
......@@ -103,6 +103,21 @@ module EE
description: 'DAST Site Profiles associated with the project',
resolve: -> (obj, _args, _ctx) { DastSiteProfilesFinder.new(project_id: obj.id).execute }
field :dast_site_validation,
::Types::DastSiteValidationType,
null: true,
resolve: -> (project, args, _ctx) do
unless ::Feature.enabled?(:security_on_demand_scans_site_validation, project)
raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled'
end
url_base = DastSiteValidation.get_normalized_url_base(args.target_url)
DastSiteValidationsFinder.new(project_id: project.id, url_base: url_base).execute.first
end,
description: 'DAST Site Validation associated with the project' do
argument :target_url, GraphQL::STRING_TYPE, required: true, description: 'target URL of the DAST Site Validation'
end
field :cluster_agent,
::Types::Clusters::AgentType,
null: true,
......
......@@ -64,9 +64,7 @@ module Mutations
end
def success_response(dast_site_validation)
status = "#{dast_site_validation.state}_VALIDATION".upcase
{ errors: [], id: dast_site_validation.to_global_id, status: status }
{ errors: [], id: dast_site_validation.to_global_id, status: dast_site_validation.state }
end
end
end
......
......@@ -28,6 +28,6 @@ module Types
field :validation_status, Types::DastSiteProfileValidationStatusEnum, null: true,
description: 'The current validation status of the site profile',
resolve: -> (obj, _args, _ctx) { "#{obj.status.upcase}_VALIDATION" }
resolve: -> (obj, _args, _ctx) { obj.status }
end
end
......@@ -2,9 +2,9 @@
module Types
class DastSiteProfileValidationStatusEnum < BaseEnum
value 'PENDING_VALIDATION', description: 'Site validation process has not started'
value 'INPROGRESS_VALIDATION', description: 'Site validation process is in progress'
value 'PASSED_VALIDATION', description: 'Site validation process finished successfully'
value 'FAILED_VALIDATION', description: 'Site validation process finished but failed'
value 'PENDING_VALIDATION', value: 'pending', description: 'Site validation process has not started'
value 'INPROGRESS_VALIDATION', value: 'inprogress', description: 'Site validation process is in progress'
value 'PASSED_VALIDATION', value: 'passed', description: 'Site validation process finished successfully'
value 'FAILED_VALIDATION', value: 'failed', description: 'Site validation process finished but failed'
end
end
# frozen_string_literal: true
module Types
class DastSiteValidationType < BaseObject
graphql_name 'DastSiteValidation'
description 'Represents a DAST Site Validation'
authorize :create_on_demand_dast_scan
field :id, ::Types::GlobalIDType[::DastSiteValidation], null: false,
description: 'ID of the site validation'
field :status, Types::DastSiteProfileValidationStatusEnum, null: false,
description: 'The status of the validation',
resolve: -> (obj, _args, _ctx) { obj.state }
end
end
# frozen_string_literal: true
class DastSiteValidationPolicy < BasePolicy
delegate { @subject.dast_site_token.project }
end
......@@ -44,9 +44,7 @@ module DastSiteTokens
end
def calculate_status(dast_site_validation)
state = dast_site_validation&.state || DastSiteValidation::INITIAL_STATE
"#{state}_VALIDATION".upcase
dast_site_validation&.state || DastSiteValidation::INITIAL_STATE.to_s
end
end
end
......@@ -67,7 +67,7 @@ RSpec.describe Mutations::DastSiteTokens::Create do
end
it 'returns the dast_site_token status' do
expect(subject[:status]).to eq('PENDING_VALIDATION')
expect(subject[:status]).to eq('pending')
end
it 'returns the dast_site_token token' do
......@@ -83,7 +83,7 @@ RSpec.describe Mutations::DastSiteTokens::Create do
target_url: target_url
)
expect(result[:status]).to eq('FAILED_VALIDATION')
expect(result[:status]).to eq('failed')
end
end
......
......@@ -68,7 +68,7 @@ RSpec.describe Mutations::DastSiteValidations::Create do
end
it 'returns the dast_site_validation status' do
expect(subject[:status]).to eq('PENDING_VALIDATION')
expect(subject[:status]).to eq(dast_site_validation.state)
end
context 'when on demand scan feature is not enabled' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['DastSiteValidation'] do
let_it_be(:dast_site_validation) { create(:dast_site_validation) }
let_it_be(:project) { dast_site_validation.dast_site_token.project }
let_it_be(:user) { create(:user) }
let_it_be(:fields) { %i[id status] }
let(:response) do
GitlabSchema.execute(
query,
context: {
current_user: user
},
variables: {
fullPath: project.full_path,
targetUrl: dast_site_validation.url_base
}
).as_json
end
before do
stub_licensed_features(security_on_demand_scans: true)
end
specify { expect(described_class.graphql_name).to eq('DastSiteValidation') }
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
it { expect(described_class).to have_graphql_fields(fields) }
describe 'dast_site_validation' do
before do
project.add_developer(user)
end
let(:query) do
%(
query project($fullPath: ID!, $targetUrl: String!) {
project(fullPath: $fullPath) {
dastSiteValidation(targetUrl: $targetUrl) {
id
status
}
}
}
)
end
describe 'status field' do
subject { response.dig('data', 'project', 'dastSiteValidation', 'status') }
it { is_expected.to eq('PENDING_VALIDATION') }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DastSiteValidationPolicy do
describe 'create_on_demand_dast_scan' do
let_it_be(:dast_site_validation, reload: true) { create(:dast_site_validation) }
let_it_be(:project) { dast_site_validation.dast_site_token.project }
let_it_be(:user) { create(:user) }
subject { described_class.new(user, dast_site_validation) }
before do
stub_licensed_features(security_on_demand_scans: true)
end
context 'when a user does not have access to the project' do
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when a user does not have access to dast_site_validations' do
before do
project.add_guest(user)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when a user has access dast_site_validations' do
before do
project.add_developer(user)
end
it { is_expected.to be_allowed(:create_on_demand_dast_scan) }
context 'when on demand scan feature flag is disabled' do
before do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when on demand scan licensed feature is not available' do
before do
stub_licensed_features(security_on_demand_scans: false)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).dastSiteValidation' do
include GraphqlHelpers
let_it_be(:dast_site_validation) { create(:dast_site_validation) }
let_it_be(:project) { dast_site_validation.dast_site_token.project }
let_it_be(:current_user) { create(:user) }
let(:query) do
%(
query project($fullPath: ID!, $targetUrl: String!) {
project(fullPath: $fullPath) {
dastSiteValidation(targetUrl: $targetUrl) {
id
status
}
}
}
)
end
subject do
post_graphql(
query,
current_user: current_user,
variables: {
fullPath: project.full_path,
targetUrl: dast_site_validation.url_base
}
)
graphql_data
end
let(:project_response) { subject['project'] }
let(:dast_site_validation_response) { project_response['dastSiteValidation'] }
before do
stub_licensed_features(security_on_demand_scans: true)
end
context 'when a user does not have access to the project' do
it 'returns a null project' do
expect(project_response).to be_nil
end
end
context 'when a user does not have access to dast_site_validation' do
it 'returns a null dast_site_validation' do
project.add_guest(current_user)
expect(dast_site_validation_response).to be_nil
end
end
context 'when a user has access to dast_site_profiles' do
before do
project.add_developer(current_user)
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(security_on_demand_scans_site_validation: false)
end
it 'returns populated edges array' do
subject
expect(graphql_errors.first['message']).to eq("Feature disabled")
end
end
it 'returns a dast_site_validation' do
expect(dast_site_validation_response['id']).to eq(dast_site_validation.to_global_id.to_s)
expect(dast_site_validation_response['status']).to eq('PENDING_VALIDATION')
end
context 'when there are multiple DAST site validations with the same target and for the same project' do
it 'returns the most recent dast_site_validation' do
new_dast_site_validation = create(:dast_site_validation, dast_site_token: dast_site_validation.dast_site_token)
expect(dast_site_validation_response['id']).to eq(new_dast_site_validation.to_global_id.to_s)
end
end
end
end
......@@ -53,7 +53,7 @@ RSpec.describe DastSiteTokens::CreateService do
end
it 'contains a status' do
expect(subject.payload[:status]).to eq('PENDING_VALIDATION')
expect(subject.payload[:status]).to eq('pending')
end
context 'when an invalid target_url is supplied' do
......
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