Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
8495368a
Commit
8495368a
authored
Jan 12, 2021
by
Philip Cunningham
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add GraphQL mutation for creating dast_profiles
Adds mutation, service, etc.
parent
0ee654c1
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
633 additions
and
0 deletions
+633
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+66
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+209
-0
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+11
-0
ee/app/graphql/ee/types/mutation_type.rb
ee/app/graphql/ee/types/mutation_type.rb
+1
-0
ee/app/graphql/mutations/dast/profiles/create.rb
ee/app/graphql/mutations/dast/profiles/create.rb
+86
-0
ee/app/services/dast/profiles/create_service.rb
ee/app/services/dast/profiles/create_service.rb
+53
-0
ee/spec/graphql/mutations/dast/profiles/create_spec.rb
ee/spec/graphql/mutations/dast/profiles/create_spec.rb
+72
-0
ee/spec/requests/api/graphql/mutations/dast/profiles/create_spec.rb
...quests/api/graphql/mutations/dast/profiles/create_spec.rb
+46
-0
ee/spec/services/dast/profiles/create_service_spec.rb
ee/spec/services/dast/profiles/create_service_spec.rb
+89
-0
No files found.
doc/api/graphql/reference/gitlab_schema.graphql
View file @
8495368a
...
...
@@ -5557,6 +5557,71 @@ type DastProfileConnection {
pageInfo
:
PageInfo
!
}
"""
Autogenerated input type of DastProfileCreate
"""
input
DastProfileCreateInput
{
"""
A
unique
identifier
for
the
client
performing
the
mutation
.
"""
clientMutationId
:
String
"""
ID
of
the
scanner
profile
to
be
associated
.
"""
dastScannerProfileId
:
DastScannerProfileID
!
"""
ID
of
the
site
profile
to
be
associated
.
"""
dastSiteProfileId
:
DastSiteProfileID
!
"""
The
description
of
the
profile
.
Defaults
to
an
empty
string
.
"""
description
:
String
=
""
"""
The
project
the
profile
belongs
to
.
"""
fullPath
:
ID
!
"""
The
name
of
the
profile
.
"""
name
:
String
!
"""
Run
scan
using
profile
after
creation
.
Defaults
to
false
.
"""
runAfterCreate
:
Boolean
=
false
}
"""
Autogenerated return type of DastProfileCreate
"""
type
DastProfileCreatePayload
{
"""
A
unique
identifier
for
the
client
performing
the
mutation
.
"""
clientMutationId
:
String
"""
The
created
profile
.
"""
dastProfile
:
DastProfile
"""
Errors
encountered
during
execution
of
the
mutation
.
"""
errors
:
[
String
!]!
"""
The
URL
of
the
pipeline
that
was
created
.
Requires
`
runAfterCreate
`
to
be
set
to
`
true
`.
"""
pipelineUrl
:
String
!
}
"""
An edge in a connection.
"""
...
...
@@ -15892,6 +15957,7 @@ type Mutation {
createSnippet
(
input
:
CreateSnippetInput
!):
CreateSnippetPayload
createTestCase
(
input
:
CreateTestCaseInput
!):
CreateTestCasePayload
dastOnDemandScanCreate
(
input
:
DastOnDemandScanCreateInput
!):
DastOnDemandScanCreatePayload
dastProfileCreate
(
input
:
DastProfileCreateInput
!):
DastProfileCreatePayload
dastScannerProfileCreate
(
input
:
DastScannerProfileCreateInput
!):
DastScannerProfileCreatePayload
dastScannerProfileDelete
(
input
:
DastScannerProfileDeleteInput
!):
DastScannerProfileDeletePayload
dastScannerProfileUpdate
(
input
:
DastScannerProfileUpdateInput
!):
DastScannerProfileUpdatePayload
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
8495368a
...
...
@@ -15215,6 +15215,188 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DastProfileCreateInput",
"description": "Autogenerated input type of DastProfileCreate",
"fields": null,
"inputFields": [
{
"name": "fullPath",
"description": "The project the profile belongs to.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "name",
"description": "The name of the profile.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "description",
"description": "The description of the profile. Defaults to an empty string.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": "\"\""
},
{
"name": "dastSiteProfileId",
"description": "ID of the site profile to be associated.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastSiteProfileID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "dastScannerProfileId",
"description": "ID of the scanner profile to be associated.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastScannerProfileID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "runAfterCreate",
"description": "Run scan using profile after creation. Defaults to false.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
},
{
"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": "DastProfileCreatePayload",
"description": "Autogenerated return type of DastProfileCreate",
"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": "dastProfile",
"description": "The created profile.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "DastProfile",
"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
},
{
"name": "pipelineUrl",
"description": "The URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastProfileEdge",
...
...
@@ -44551,6 +44733,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastProfileCreate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "DastProfileCreateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastProfileCreatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastScannerProfileCreate",
"description": null,
doc/api/graphql/reference/index.md
View file @
8495368a
...
...
@@ -891,6 +891,17 @@ Represents a DAST Profile.
|
`id`
| DastProfileID! | ID of the profile. |
|
`name`
| String | The name of the profile. |
### DastProfileCreatePayload
Autogenerated return type of DastProfileCreate.
| Field | Type | Description |
| ----- | ---- | ----------- |
|
`clientMutationId`
| String | A unique identifier for the client performing the mutation. |
|
`dastProfile`
| DastProfile | The created profile. |
|
`errors`
| String! => Array | Errors encountered during execution of the mutation. |
|
`pipelineUrl`
| String! | The URL of the pipeline that was created. Requires
`runAfterCreate`
to be set to
`true`
. |
### DastScannerProfile
Represents a DAST scanner profile.
...
...
ee/app/graphql/ee/types/mutation_type.rb
View file @
8495368a
...
...
@@ -40,6 +40,7 @@ module EE
mount_mutation
::
Mutations
::
InstanceSecurityDashboard
::
AddProject
mount_mutation
::
Mutations
::
InstanceSecurityDashboard
::
RemoveProject
mount_mutation
::
Mutations
::
DastOnDemandScans
::
Create
mount_mutation
::
Mutations
::
Dast
::
Profiles
::
Create
mount_mutation
::
Mutations
::
DastSiteProfiles
::
Create
mount_mutation
::
Mutations
::
DastSiteProfiles
::
Update
mount_mutation
::
Mutations
::
DastSiteProfiles
::
Delete
...
...
ee/app/graphql/mutations/dast/profiles/create.rb
0 → 100644
View file @
8495368a
# frozen_string_literal: true
module
Mutations
module
Dast
module
Profiles
class
Create
<
BaseMutation
include
FindsProject
graphql_name
'DastProfileCreate'
field
:dast_profile
,
::
Types
::
Dast
::
ProfileType
,
null:
true
,
description:
'The created profile.'
field
:pipeline_url
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'The URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`.'
argument
:full_path
,
GraphQL
::
ID_TYPE
,
required:
true
,
description:
'The project the profile belongs to.'
argument
:name
,
GraphQL
::
STRING_TYPE
,
required:
true
,
description:
'The name of the profile.'
argument
:description
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'The description of the profile. Defaults to an empty string.'
,
default_value:
''
argument
:dast_site_profile_id
,
::
Types
::
GlobalIDType
[
::
DastSiteProfile
],
required:
true
,
description:
'ID of the site profile to be associated.'
argument
:dast_scanner_profile_id
,
::
Types
::
GlobalIDType
[
::
DastScannerProfile
],
required:
true
,
description:
'ID of the scanner profile to be associated.'
argument
:run_after_create
,
GraphQL
::
BOOLEAN_TYPE
,
required:
false
,
description:
'Run scan using profile after creation. Defaults to false.'
,
default_value:
false
authorize
:create_on_demand_dast_scan
def
resolve
(
full_path
:,
name
:,
description:
''
,
dast_site_profile_id
:,
dast_scanner_profile_id
:,
run_after_create:
false
)
project
=
authorized_find!
(
full_path
)
raise
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
,
'Feature disabled'
unless
allowed?
(
project
)
# TODO: remove explicit coercion once compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
site_profile_id
=
::
Types
::
GlobalIDType
[
::
DastSiteProfile
].
coerce_isolated_input
(
dast_site_profile_id
)
scanner_profile_id
=
::
Types
::
GlobalIDType
[
::
DastScannerProfile
].
coerce_isolated_input
(
dast_scanner_profile_id
)
dast_site_profile
=
project
.
dast_site_profiles
.
find
(
site_profile_id
.
model_id
)
dast_scanner_profile
=
project
.
dast_scanner_profiles
.
find
(
scanner_profile_id
.
model_id
)
response
=
::
Dast
::
Profiles
::
CreateService
.
new
(
container:
project
,
current_user:
current_user
,
params:
{
project:
project
,
name:
name
,
description:
description
,
dast_site_profile:
dast_site_profile
,
dast_scanner_profile:
dast_scanner_profile
,
run_after_create:
run_after_create
}
).
execute
return
{
errors:
response
.
errors
}
if
response
.
error?
{
errors:
[],
dast_profile:
response
.
payload
.
fetch
(
:dast_profile
),
pipeline_url:
response
.
payload
.
fetch
(
:pipeline_url
)
}
end
private
def
allowed?
(
project
)
project
.
feature_available?
(
:security_on_demand_scans
)
&&
Feature
.
enabled?
(
:dast_saved_scans
,
project
,
default_enabled: :yaml
)
end
end
end
end
end
ee/app/services/dast/profiles/create_service.rb
0 → 100644
View file @
8495368a
# frozen_string_literal: true
module
Dast
module
Profiles
class
CreateService
<
BaseContainerService
def
execute
return
ServiceResponse
.
error
(
message:
'Insufficient permissions'
)
unless
allowed?
dast_profile
=
Dast
::
Profile
.
create!
(
project:
container
,
name:
params
.
fetch
(
:name
),
description:
params
.
fetch
(
:description
),
dast_site_profile:
dast_site_profile
,
dast_scanner_profile:
dast_scanner_profile
)
return
ServiceResponse
.
success
(
payload:
{
dast_profile:
dast_profile
,
pipeline_url:
nil
})
unless
params
.
fetch
(
:run_after_create
)
response
=
::
DastOnDemandScans
::
CreateService
.
new
(
container:
container
,
current_user:
current_user
,
params:
{
dast_site_profile:
dast_site_profile
,
dast_scanner_profile:
dast_scanner_profile
}
).
execute
return
response
if
response
.
error?
ServiceResponse
.
success
(
payload:
{
dast_profile:
dast_profile
,
pipeline_url:
response
.
payload
.
fetch
(
:pipeline_url
)
})
rescue
ActiveRecord
::
RecordInvalid
=>
err
ServiceResponse
.
error
(
message:
err
.
record
.
errors
.
full_messages
)
rescue
KeyError
=>
err
ServiceResponse
.
error
(
message:
err
.
message
.
capitalize
)
end
private
def
allowed?
container
.
feature_available?
(
:security_on_demand_scans
)
&&
Feature
.
enabled?
(
:dast_saved_scans
,
container
,
default_enabled: :yaml
)
end
def
dast_site_profile
@dast_site_profile
||=
params
.
fetch
(
:dast_site_profile
)
end
def
dast_scanner_profile
@dast_scanner_profile
||=
params
.
fetch
(
:dast_scanner_profile
)
end
end
end
end
ee/spec/graphql/mutations/dast/profiles/create_spec.rb
0 → 100644
View file @
8495368a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Mutations
::
Dast
::
Profiles
::
Create
do
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let_it_be
(
:developer
)
{
create
(
:user
,
developer_projects:
[
project
]
)
}
let_it_be
(
:dast_site_profile
)
{
create
(
:dast_site_profile
,
project:
project
)
}
let_it_be
(
:dast_scanner_profile
)
{
create
(
:dast_scanner_profile
,
project:
project
)
}
let
(
:name
)
{
SecureRandom
.
hex
}
let
(
:description
)
{
SecureRandom
.
hex
}
let
(
:run_after_create
)
{
false
}
let
(
:dast_profile
)
{
Dast
::
Profile
.
find_by
(
project:
project
,
name:
name
)
}
subject
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
developer
},
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:
project
.
full_path
,
name:
name
,
description:
description
,
dast_site_profile_id:
dast_site_profile
.
to_global_id
.
to_s
,
dast_scanner_profile_id:
dast_scanner_profile
.
to_global_id
.
to_s
,
run_after_create:
run_after_create
)
end
context
'when the feature is licensed'
do
context
'when the feature is enabled'
do
it
'raises an exception'
do
stub_feature_flags
(
dast_saved_scans:
false
)
expect
{
subject
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
end
end
context
'when the user can run a dast scan'
do
it
'returns the dast_profile'
do
expect
(
subject
[
:dast_profile
]).
to
eq
(
dast_profile
)
end
context
'when run_after_create=true'
do
let
(
:run_after_create
)
{
true
}
it
'returns the pipeline_url'
do
actual_url
=
subject
[
:pipeline_url
]
pipeline
=
Ci
::
Pipeline
.
find_by
(
project:
project
,
sha:
project
.
repository
.
commit
.
sha
,
source: :ondemand_dast_scan
,
config_source: :parameter_source
)
expected_url
=
Rails
.
application
.
routes
.
url_helpers
.
project_pipeline_url
(
project
,
pipeline
)
expect
(
actual_url
).
to
eq
(
expected_url
)
end
end
end
end
end
end
ee/spec/requests/api/graphql/mutations/dast/profiles/create_spec.rb
0 → 100644
View file @
8495368a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'Creating a DAST Profile'
do
include
GraphqlHelpers
let
(
:name
)
{
SecureRandom
.
hex
}
let
(
:dast_site_profile
)
{
create
(
:dast_site_profile
,
project:
project
)
}
let
(
:dast_scanner_profile
)
{
create
(
:dast_scanner_profile
,
project:
project
)
}
let
(
:dast_profile
)
{
Dast
::
Profile
.
find_by
(
project:
project
,
name:
name
)
}
let
(
:mutation_name
)
{
:dast_profile_create
}
let
(
:mutation
)
do
graphql_mutation
(
mutation_name
,
full_path:
full_path
,
name:
name
,
dast_site_profile_id:
global_id_of
(
dast_site_profile
),
dast_scanner_profile_id:
global_id_of
(
dast_scanner_profile
),
run_after_create:
true
)
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
'returns dastProfile.id'
do
subject
expect
(
mutation_response
.
dig
(
'dastProfile'
,
'id'
)).
to
eq
(
global_id_of
(
dast_profile
))
end
it
'returns dastProfile.editPath'
do
subject
expect
(
mutation_response
.
dig
(
'dastProfile'
,
'editPath'
)).
to
eq
(
edit_project_on_demand_scan_path
(
project
,
dast_profile
))
end
it
'returns a non-empty pipelineUrl'
do
subject
expect
(
mutation_response
[
'pipelineUrl'
]).
not_to
be_blank
end
end
end
ee/spec/services/dast/profiles/create_service_spec.rb
0 → 100644
View file @
8495368a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Dast
::
Profiles
::
CreateService
do
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let_it_be
(
:developer
)
{
create
(
:user
,
developer_projects:
[
project
]
)
}
let_it_be
(
:dast_site_profile
)
{
create
(
:dast_site_profile
,
project:
project
)
}
let_it_be
(
:dast_scanner_profile
)
{
create
(
:dast_scanner_profile
,
project:
project
)
}
let_it_be
(
:default_params
)
do
{
name:
SecureRandom
.
hex
,
description: :description
,
dast_site_profile:
dast_site_profile
,
dast_scanner_profile:
dast_scanner_profile
,
run_after_create:
false
}
end
let
(
:params
)
{
default_params
}
subject
{
described_class
.
new
(
container:
project
,
current_user:
developer
,
params:
params
).
execute
}
describe
'execute'
do
before
do
project
.
clear_memoization
(
:licensed_feature_available
)
end
context
'when on demand scan feature is disabled'
do
it
'communicates failure'
do
stub_licensed_features
(
security_on_demand_scans:
true
)
stub_feature_flags
(
dast_saved_scans:
false
)
aggregate_failures
do
expect
(
subject
.
status
).
to
eq
(
:error
)
expect
(
subject
.
message
).
to
eq
(
'Insufficient permissions'
)
end
end
end
context
'when on demand scan licensed feature is not available'
do
it
'communicates failure'
do
stub_licensed_features
(
security_on_demand_scans:
false
)
stub_feature_flags
(
dast_saved_scans:
true
)
aggregate_failures
do
expect
(
subject
.
status
).
to
eq
(
:error
)
expect
(
subject
.
message
).
to
eq
(
'Insufficient permissions'
)
end
end
end
context
'when the feature is enabled'
do
before
do
stub_licensed_features
(
security_on_demand_scans:
true
)
stub_feature_flags
(
dast_saved_scans:
true
)
end
it
'communicates success'
do
expect
(
subject
.
status
).
to
eq
(
:success
)
end
it
'creates a dast_profile'
do
expect
{
subject
}.
to
change
{
Dast
::
Profile
.
count
}.
by
(
1
)
end
context
'when param run_after_create: true'
do
let
(
:params
)
{
default_params
.
merge
(
run_after_create:
true
)
}
it
'calls DastOnDemandScans::CreateService'
do
params
=
{
dast_site_profile:
dast_site_profile
,
dast_scanner_profile:
dast_scanner_profile
}
expect
(
DastOnDemandScans
::
CreateService
).
to
receive
(
:new
).
with
(
hash_including
(
params:
params
)).
and_call_original
subject
end
it
'creates a ci_pipeline'
do
expect
{
subject
}.
to
change
{
Ci
::
Pipeline
.
count
}.
by
(
1
)
end
end
context
'when a param is missing'
do
let
(
:params
)
{
default_params
.
except
(
:run_after_create
)
}
it
'communicates failure'
do
aggregate_failures
do
expect
(
subject
.
status
).
to
eq
(
:error
)
expect
(
subject
.
message
).
to
eq
(
'Key not found: :run_after_create'
)
end
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment