Commit 9d89116e authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'add_dast_site_validation_query_245213' into 'master'

Add DAST Site Validation GraphQL query endpoint

See merge request gitlab-org/gitlab!45343
parents b54f3cb0 4ba72a64
......@@ -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