Commit b4455ad6 authored by Craig Smith's avatar Craig Smith

List the DAST scanner profiles for a project

Add GraphQL query to list the DAST scanner
profiles associated with a project, so that it can
be used to run a DAST on-demand scan
parent 354bfa4f
......@@ -2264,6 +2264,51 @@ enum DastScanTypeEnum {
PASSIVE
}
"""
Represents a DAST scanner profile.
"""
type DastScannerProfile {
"""
ID of the DAST scanner profile
"""
id: ID!
"""
Name of the DAST scanner profile
"""
profileName: String
"""
The maximum number of seconds allowed for the spider to traverse the site
"""
spiderTimeout: Int
"""
The maximum number of seconds allowed for the site under test to respond to a request
"""
targetTimeout: Int
}
"""
The connection type for DastScannerProfile.
"""
type DastScannerProfileConnection {
"""
A list of edges.
"""
edges: [DastScannerProfileEdge]
"""
A list of nodes.
"""
nodes: [DastScannerProfile]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
Autogenerated input type of DastScannerProfileCreate
"""
......@@ -2314,6 +2359,21 @@ type DastScannerProfileCreatePayload {
id: ID
}
"""
An edge in a connection.
"""
type DastScannerProfileEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: DastScannerProfile
}
"""
Represents a DAST Site Profile.
"""
......@@ -9672,6 +9732,31 @@ type Project {
"""
createdAt: Time
"""
The DAST scanner profiles associated with the project
"""
dastScannerProfiles(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): DastScannerProfileConnection
"""
DAST Site Profiles associated with the project
"""
......
......@@ -6078,6 +6078,146 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastScannerProfile",
"description": "Represents a DAST scanner profile.",
"fields": [
{
"name": "id",
"description": "ID of the DAST scanner profile",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "profileName",
"description": "Name of the DAST scanner profile",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "spiderTimeout",
"description": "The maximum number of seconds allowed for the spider to traverse the site",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "targetTimeout",
"description": "The maximum number of seconds allowed for the site under test to respond to a request",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastScannerProfileConnection",
"description": "The connection type for DastScannerProfile.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "DastScannerProfileEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "DastScannerProfile",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DastScannerProfileCreateInput",
......@@ -6214,6 +6354,51 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastScannerProfileEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "DastScannerProfile",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastSiteProfile",
......@@ -28963,6 +29148,59 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastScannerProfiles",
"description": "The DAST scanner profiles associated with the project",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastScannerProfileConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteProfiles",
"description": "DAST Site Profiles associated with the project",
......@@ -392,6 +392,17 @@ Autogenerated return type of DastOnDemandScanCreate
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `pipelineUrl` | String | URL of the pipeline that was created. |
## DastScannerProfile
Represents a DAST scanner profile.
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | ID of the DAST scanner profile |
| `profileName` | String | Name of the DAST scanner profile |
| `spiderTimeout` | Int | The maximum number of seconds allowed for the spider to traverse the site |
| `targetTimeout` | Int | The maximum number of seconds allowed for the site under test to respond to a request |
## DastScannerProfileCreatePayload
Autogenerated return type of DastScannerProfileCreate
......
......@@ -12,6 +12,16 @@ module EE
project
end
field :dast_scanner_profiles,
::Types::DastScannerProfileType.connection_type,
null: true,
description: 'The DAST scanner profiles associated with the project',
resolve: -> (project, _args, _ctx) do
return DastScannerProfile.none unless ::Feature.enabled?(:security_on_demand_scans_feature_flag, project)
project.dast_scanner_profiles
end
field :vulnerabilities,
::Types::VulnerabilityType.connection_type,
null: true,
......
# frozen_string_literal: true
module Types
class DastScannerProfileType < BaseObject
graphql_name 'DastScannerProfile'
description 'Represents a DAST scanner profile.'
authorize :run_ondemand_dast_scan
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the DAST scanner profile'
field :profile_name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the DAST scanner profile',
method: :name
field :spider_timeout, GraphQL::INT_TYPE, null: true,
description: 'The maximum number of seconds allowed for the spider to traverse the site'
field :target_timeout, GraphQL::INT_TYPE, null: true,
description: 'The maximum number of seconds allowed for the site under test to respond to a request'
end
end
......@@ -57,6 +57,7 @@ module EE
has_many :audit_events, as: :entity
has_many :path_locks
has_many :requirements, inverse_of: :project, class_name: 'RequirementsManagement::Requirement'
has_many :dast_scanner_profiles
# the rationale behind vulnerabilities and vulnerability_findings can be found here:
# https://gitlab.com/gitlab-org/gitlab/issues/10252#terminology
......
# frozen_string_literal: true
class DastScannerProfilePolicy < BasePolicy
delegate { @subject.project }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['DastScannerProfile'] do
let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile) }
let_it_be(:project) { dast_scanner_profile.project }
let_it_be(:user) { create(:user) }
let_it_be(:fields) { %i[id profileName spiderTimeout targetTimeout] }
let(:response) do
GitlabSchema.execute(
query,
context: {
current_user: user
},
variables: {
fullPath: project.full_path
}
).as_json
end
subject { response }
before do
stub_licensed_features(security_on_demand_scans: true)
end
specify { expect(described_class.graphql_name).to eq('DastScannerProfile') }
specify { expect(described_class).to require_graphql_authorizations(:run_ondemand_dast_scan) }
it { expect(described_class).to have_graphql_fields(fields) }
describe 'dast_scanner_profiles' do
before do
project.add_developer(user)
end
let(:query) do
%(
query project($fullPath: ID!) {
project(fullPath: $fullPath) {
dastScannerProfiles(first: 1) {
nodes {
id
profileName
targetTimeout
spiderTimeout
}
}
}
}
)
end
let(:first_dast_scanner_profile) do
response.dig('data', 'project', 'dastScannerProfiles', 'nodes', 0)
end
describe 'profile_name field' do
subject { first_dast_scanner_profile['profileName'] }
it { is_expected.to eq(dast_scanner_profile.name) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).dastScannerProfiles' do
include GraphqlHelpers
let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile) }
let_it_be(:project) { dast_scanner_profile.project }
let_it_be(:current_user) { create(:user) }
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('dastScannerProfiles', {}, "nodes { #{all_graphql_fields_for('DastScannerProfile')} }")
)
end
let(:response_data) do
post_graphql(
query,
current_user: current_user,
variables: {
fullPath: project.full_path
}
)
graphql_data
end
before do
stub_licensed_features(security_on_demand_scans: true)
end
context 'when a user does not have access to the project' do
describe 'project response' do
subject { response_data.dig('project') }
it { is_expected.to be_nil }
end
end
context 'when a user does not have access to run_ondemand_dast_scan' do
before do
project.add_guest(current_user)
end
describe 'dast scanner profiles' do
subject { response_data.dig('project', 'dastScannerProfiles', 'nodes') }
it { is_expected.to be_empty }
end
end
context 'when a user has access dast_scanner_profiles' do
before do
project.add_developer(current_user)
end
describe 'dast scanner profiles' do
subject { response_data.dig('project', 'dastScannerProfiles', 'nodes') }
it { is_expected.not_to be_empty }
end
describe 'first dast scanner profile id' do
subject { response_data.dig('project', 'dastScannerProfiles', 'nodes').first['id'] }
it { is_expected.to eq(dast_scanner_profile.to_global_id.to_s) }
end
context 'when on demand scan feature flag is disabled' do
before do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
end
describe 'dast scanner profiles' do
subject { response_data.dig('project', 'dastScannerProfiles', 'nodes') }
it { is_expected.to be_empty }
end
end
end
end
......@@ -466,6 +466,7 @@ project:
- vulnerability_identifiers
- vulnerability_scanners
- dast_site_profiles
- dast_scanner_profiles
- dast_sites
- operations_feature_flags
- operations_feature_flags_client
......
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