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
4b35f2ac
Commit
4b35f2ac
authored
Oct 10, 2018
by
Fabien Catteau
Committed by
Kamil Trzciński
Oct 10, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
API endpoints for Group-level Security Dashboard
parent
f2447c88
Changes
30
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
882 additions
and
51 deletions
+882
-51
config/routes/group.rb
config/routes/group.rb
+5
-0
db/fixtures/development/20_vulnerabilities.rb
db/fixtures/development/20_vulnerabilities.rb
+136
-0
ee/app/controllers/groups/security/vulnerabilities_controller.rb
...controllers/groups/security/vulnerabilities_controller.rb
+41
-0
ee/app/controllers/projects/security/dashboard_controller.rb
ee/app/controllers/projects/security/dashboard_controller.rb
+3
-3
ee/app/models/ee/group.rb
ee/app/models/ee/group.rb
+4
-0
ee/app/models/ee/project.rb
ee/app/models/ee/project.rb
+0
-7
ee/app/models/license.rb
ee/app/models/license.rb
+1
-0
ee/app/models/vulnerabilities/occurrence.rb
ee/app/models/vulnerabilities/occurrence.rb
+70
-2
ee/app/policies/ee/group_policy.rb
ee/app/policies/ee/group_policy.rb
+12
-0
ee/app/policies/ee/project_policy.rb
ee/app/policies/ee/project_policy.rb
+10
-2
ee/app/serializers/vulnerabilities/identifier_entity.rb
ee/app/serializers/vulnerabilities/identifier_entity.rb
+8
-0
ee/app/serializers/vulnerabilities/occurrence_entity.rb
ee/app/serializers/vulnerabilities/occurrence_entity.rb
+33
-0
ee/app/serializers/vulnerabilities/occurrence_serializer.rb
ee/app/serializers/vulnerabilities/occurrence_serializer.rb
+5
-0
ee/app/serializers/vulnerabilities/scanner_entity.rb
ee/app/serializers/vulnerabilities/scanner_entity.rb
+6
-0
ee/app/serializers/vulnerability_summary_entity.rb
ee/app/serializers/vulnerability_summary_entity.rb
+19
-0
ee/app/serializers/vulnerability_summary_serializer.rb
ee/app/serializers/vulnerability_summary_serializer.rb
+3
-0
ee/lib/gitlab/vulnerabilities/occurrences_preloader.rb
ee/lib/gitlab/vulnerabilities/occurrences_preloader.rb
+16
-0
ee/spec/controllers/groups/security/vulnerabilities_controller_spec.rb
...ollers/groups/security/vulnerabilities_controller_spec.rb
+178
-0
ee/spec/controllers/projects/security/dashboard_controller_spec.rb
...ontrollers/projects/security/dashboard_controller_spec.rb
+14
-8
ee/spec/factories/vulnerabilities/occurrences.rb
ee/spec/factories/vulnerabilities/occurrences.rb
+25
-3
ee/spec/fixtures/api/schemas/vulnerabilities/occurrence.json
ee/spec/fixtures/api/schemas/vulnerabilities/occurrence.json
+79
-0
ee/spec/fixtures/api/schemas/vulnerabilities/occurrence_list.json
...fixtures/api/schemas/vulnerabilities/occurrence_list.json
+4
-0
ee/spec/fixtures/api/schemas/vulnerabilities/summary.json
ee/spec/fixtures/api/schemas/vulnerabilities/summary.json
+16
-0
ee/spec/fixtures/api/schemas/vulnerabilities/summary_for_report_type.json
.../api/schemas/vulnerabilities/summary_for_report_type.json
+14
-0
ee/spec/models/project_spec.rb
ee/spec/models/project_spec.rb
+0
-23
ee/spec/policies/group_policy_spec.rb
ee/spec/policies/group_policy_spec.rb
+64
-0
ee/spec/policies/project_policy_spec.rb
ee/spec/policies/project_policy_spec.rb
+3
-3
ee/spec/serializers/vulnerabilities/identifier_entity_spec.rb
...pec/serializers/vulnerabilities/identifier_entity_spec.rb
+17
-0
ee/spec/serializers/vulnerabilities/occurrence_entity_spec.rb
...pec/serializers/vulnerabilities/occurrence_entity_spec.rb
+79
-0
ee/spec/serializers/vulnerabilities/scanner_entity_spec.rb
ee/spec/serializers/vulnerabilities/scanner_entity_spec.rb
+17
-0
No files found.
config/routes/group.rb
View file @
4b35f2ac
...
...
@@ -78,6 +78,11 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
# EE-specific start
namespace
:security
do
resource
:dashboard
,
only:
[
:show
],
controller: :dashboard
resources
:vulnerabilities
,
only:
[
:index
],
controller: :vulnerabilities
do
collection
do
get
:summary
end
end
end
# EE-specific end
...
...
db/fixtures/development/20_vulnerabilities.rb
0 → 100644
View file @
4b35f2ac
require
'./spec/support/sidekiq'
class
Gitlab::Seeder::Vulnerabilities
attr_reader
:project
def
initialize
(
project
)
@project
=
project
end
def
seed!
return
unless
pipeline
10
.
times
do
|
rank
|
occurrence
=
create_occurrence
(
rank
)
create_occurrence_identifier
(
occurrence
,
rank
,
primary:
true
)
create_occurrence_identifier
(
occurrence
,
rank
)
if
author
case
rank
%
3
when
0
create_feedback
(
occurrence
,
'dismissal'
)
when
1
create_feedback
(
occurrence
,
'issue'
)
else
# no feedback
end
end
end
end
private
def
create_occurrence
(
rank
)
project
.
vulnerabilities
.
create!
(
uuid:
random_uuid
,
name:
'Cipher with no integrity'
,
pipeline:
pipeline
,
ref:
project
.
default_branch
,
report_type: :sast
,
severity:
random_level
,
confidence:
random_level
,
project_fingerprint:
random_fingerprint
,
primary_identifier_fingerprint:
random_fingerprint
,
location_fingerprint:
random_fingerprint
,
raw_metadata:
metadata
(
rank
).
to_json
,
metadata_version:
'sast:1.0'
,
scanner:
scanner
)
end
def
create_occurrence_identifier
(
occurrence
,
key
,
primary:
false
)
type
=
primary
?
'primary'
:
'secondary'
fingerprint
=
if
primary
occurrence
.
primary_identifier_fingerprint
else
Digest
::
SHA1
.
hexdigest
(
"sid_fingerprint-
#{
project
.
id
}
-
#{
key
}
"
)
end
project
.
vulnerability_identifiers
.
create!
(
external_type:
"
#{
type
.
upcase
}
_SECURITY_ID"
,
external_id:
"
#{
type
.
upcase
}
_SECURITY_
#{
key
}
"
,
fingerprint:
fingerprint
,
name:
"
#{
type
.
capitalize
}
#{
key
}
"
,
url:
"https://security.example.com/
#{
type
.
downcase
}
/
#{
key
}
"
)
end
def
create_feedback
(
occurrence
,
type
)
issue
=
create_issue
(
"Dismiss
#{
occurrence
.
name
}
"
)
if
type
==
'issue'
project
.
vulnerability_feedback
.
create!
(
feedback_type:
type
,
category:
'sast'
,
author:
author
,
issue:
issue
,
pipeline:
pipeline
,
project_fingerprint:
occurrence
.
project_fingerprint
,
vulnerability_data:
{
category:
'sast'
})
end
def
scanner
@scanner
||=
project
.
vulnerability_scanners
.
create!
(
project:
project
,
external_id:
'security-scanner'
,
name:
'Security Scanner'
)
end
def
create_issue
(
title
)
project
.
issues
.
create!
(
author:
author
,
title:
title
)
end
def
random_level
::
Vulnerabilities
::
Occurrence
::
LEVELS
.
keys
.
sample
end
def
metadata
(
line
)
{
description:
"The cipher does not provide data integrity update 1"
,
solution:
"GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result."
,
location:
{
file:
"maven/src/main/java//App.java"
,
start_line:
line
,
end_line:
line
,
class:
"com.gitlab..App"
,
method:
"insecureCypher"
},
links:
[
{
name:
"Cipher does not check for integrity first?"
,
url:
"https://crypto.stackexchange.com/questions/31428/pbewithmd5anddes-cipher-does-not-check-for-integrity-first"
}
]
}
end
def
random_uuid
SecureRandom
.
hex
(
18
)
end
def
random_fingerprint
SecureRandom
.
hex
(
20
)
end
def
pipeline
@pipeline
||=
project
.
pipelines
.
where
(
ref:
project
.
default_branch
).
last
end
def
author
@author
||=
project
.
users
.
first
end
end
Gitlab
::
Seeder
.
quiet
do
Project
.
joins
(
:pipelines
).
uniq
.
all
.
sample
(
5
).
each
do
|
project
|
seeder
=
Gitlab
::
Seeder
::
Vulnerabilities
.
new
(
project
)
seeder
.
seed!
end
end
ee/app/controllers/groups/security/vulnerabilities_controller.rb
0 → 100644
View file @
4b35f2ac
# frozen_string_literal: true
class
Groups::Security::VulnerabilitiesController
<
Groups
::
ApplicationController
before_action
:ensure_security_dashboard_feature_enabled
before_action
:authorize_read_group_security_dashboard!
def
index
@vulnerabilities
=
group
.
all_vulnerabilities
.
ordered
.
page
(
params
[
:page
])
.
per
(
10
)
.
to_a
::
Gitlab
::
Vulnerabilities
::
OccurrencesPreloader
.
new
.
preload
(
@vulnerabilities
)
# rubocop:disable CodeReuse/ActiveRecord
respond_to
do
|
format
|
format
.
json
do
render
json:
Vulnerabilities
::
OccurrenceSerializer
.
new
(
current_user:
@current_user
)
.
with_pagination
(
request
,
response
)
.
represent
(
@vulnerabilities
)
end
end
end
def
summary
respond_to
do
|
format
|
format
.
json
do
render
json:
VulnerabilitySummarySerializer
.
new
.
represent
(
group
)
end
end
end
private
def
ensure_security_dashboard_feature_enabled
render_404
unless
@group
.
feature_available?
(
:security_dashboard
)
end
def
authorize_read_group_security_dashboard!
render_403
unless
can?
(
current_user
,
:read_group_security_dashboard
,
group
)
end
end
ee/app/controllers/projects/security/dashboard_controller.rb
View file @
4b35f2ac
module
Projects
module
Security
class
DashboardController
<
Projects
::
ApplicationController
before_action
:ensure_security_
features
_enabled
before_action
:ensure_security_
dashboard_feature
_enabled
before_action
:authorize_read_project_security_dashboard!
def
show
...
...
@@ -10,8 +10,8 @@ module Projects
private
def
ensure_security_
features
_enabled
render_404
unless
@project
.
security_reports_feature_available?
def
ensure_security_
dashboard_feature
_enabled
render_404
unless
@project
.
feature_available?
(
:security_dashboard
)
end
end
end
...
...
ee/app/models/ee/group.rb
View file @
4b35f2ac
...
...
@@ -68,6 +68,10 @@ module EE
end
end
def
all_vulnerabilities
Vulnerabilities
::
Occurrence
.
where
(
project:
all_projects
)
end
def
human_ldap_access
::
Gitlab
::
Access
.
options_with_owner
.
key
(
ldap_access
)
end
...
...
ee/app/models/ee/project.rb
View file @
4b35f2ac
...
...
@@ -104,13 +104,6 @@ module EE
end
end
def
security_reports_feature_available?
feature_available?
(
:sast
)
||
feature_available?
(
:dependency_scanning
)
||
feature_available?
(
:sast_container
)
||
feature_available?
(
:dast
)
end
def
latest_pipeline_with_security_reports
pipelines
.
newest_first
(
default_branch
).
with_security_reports
.
first
end
...
...
ee/app/models/license.rb
View file @
4b35f2ac
...
...
@@ -72,6 +72,7 @@ class License < ActiveRecord::Base
]
.
freeze
EEU_FEATURES
=
EEP_FEATURES
+
%i[
security_dashboard
dependency_scanning
license_management
sast
...
...
ee/app/models/vulnerabilities/occurrence.rb
View file @
4b35f2ac
...
...
@@ -3,6 +3,7 @@
module
Vulnerabilities
class
Occurrence
<
ActiveRecord
::
Base
include
ShaAttribute
include
::
Gitlab
::
Utils
::
StrongMemoize
self
.
table_name
=
"vulnerability_occurrences"
...
...
@@ -29,12 +30,14 @@ module Vulnerabilities
has_many
:occurrence_identifiers
,
class_name:
'Vulnerabilities::OccurrenceIdentifier'
has_many
:identifiers
,
through: :occurrence_identifiers
,
class_name:
'Vulnerabilities::Identifier'
enum
report_type:
{
REPORT_TYPES
=
{
sast:
0
,
dependency_scanning:
1
,
container_scanning:
2
,
dast:
3
}
}.
with_indifferent_access
.
freeze
enum
report_type:
REPORT_TYPES
validates
:scanner
,
presence:
true
validates
:project
,
presence:
true
...
...
@@ -56,6 +59,45 @@ module Vulnerabilities
validates
:metadata_version
,
presence:
true
validates
:raw_metadata
,
presence:
true
scope
:ordered
,
->
{
order
(
"severity desc"
,
:id
)
}
scope
:counted_by_report_and_severity
,
->
{
group
(
:report_type
,
:severity
).
count
}
def
feedback
(
feedback_type
:)
params
=
{
project_id:
project_id
,
category:
report_type
,
project_fingerprint:
project_fingerprint
,
feedback_type:
feedback_type
}
BatchLoader
.
for
(
params
).
batch
do
|
items
,
loader
|
project_ids
=
items
.
group_by
{
|
i
|
i
[
:project_id
]
}
categories
=
items
.
group_by
{
|
i
|
i
[
:category
]
}
fingerprints
=
items
.
group_by
{
|
i
|
i
[
:project_fingerprint
]
}
VulnerabilityFeedback
.
where
(
project_id:
project_ids
.
keys
,
category:
categories
.
keys
,
project_fingerprint:
fingerprints
.
keys
).
find_each
do
|
feedback
|
loaded_params
=
{
project_id:
feedback
.
project_id
,
category:
feedback
.
category
,
project_fingerprint:
feedback
.
project_fingerprint
,
feedback_type:
feedback
.
feedback_type
}
loader
.
call
(
loaded_params
,
feedback
)
end
end
end
def
dismissal_feedback
feedback
(
feedback_type:
'dismissal'
)
end
def
issue_feedback
feedback
(
feedback_type:
'issue'
)
end
# Override getter and setter for :severity as we can't use enum (it conflicts with :confidence)
# To be replaced with enum using _prefix when migrating to rails 5
def
severity
...
...
@@ -75,5 +117,31 @@ module Vulnerabilities
def
confidence
=
(
confidence
)
write_attribute
(
:confidence
,
LEVELS
[
confidence
])
end
def
metadata
strong_memoize
(
:metadata
)
do
begin
JSON
.
parse
(
raw_metadata
)
rescue
JSON
::
ParserError
{}
end
end
end
def
description
metadata
.
dig
(
'description'
)
end
def
solution
metadata
.
dig
(
'solution'
)
end
def
location
metadata
.
fetch
(
'location'
,
{})
end
def
links
metadata
.
fetch
(
'links'
,
[])
end
end
end
ee/app/policies/ee/group_policy.rb
View file @
4b35f2ac
...
...
@@ -25,6 +25,10 @@ module EE
.
allow_group_owners_to_manage_ldap
end
condition
(
:security_dashboard_feature_disabled
)
do
!
@subject
.
feature_available?
(
:security_dashboard
)
end
rule
{
reporter
}.
policy
do
enable
:admin_list
enable
:admin_board
...
...
@@ -65,6 +69,14 @@ module EE
rule
{
project_creation_level_enabled
&
developer
&
developer_maintainer_access
}.
enable
:create_projects
rule
{
project_creation_level_enabled
&
create_projects_disabled
}.
prevent
:create_projects
rule
{
developer
}.
policy
do
enable
:read_group_security_dashboard
end
rule
{
security_dashboard_feature_disabled
}.
policy
do
prevent
:read_group_security_dashboard
end
end
end
end
ee/app/policies/ee/project_policy.rb
View file @
4b35f2ac
...
...
@@ -55,7 +55,9 @@ module EE
end
with_scope
:subject
condition
(
:security_reports_feature_available
)
{
@subject
.
security_reports_feature_available?
}
condition
(
:security_dashboard_feature_disabled
)
do
!
@subject
.
feature_available?
(
:security_dashboard
)
end
condition
(
:prometheus_alerts_enabled
)
do
@subject
.
feature_available?
(
:prometheus_alerts
,
@user
)
...
...
@@ -114,7 +116,13 @@ module EE
rule
{
can?
(
:public_access
)
}.
enable
:read_package
rule
{
can?
(
:developer_access
)
&
security_reports_feature_available
}.
enable
:read_project_security_dashboard
rule
{
can?
(
:developer_access
)
}.
policy
do
enable
:read_project_security_dashboard
end
rule
{
security_dashboard_feature_disabled
}.
policy
do
prevent
:read_project_security_dashboard
end
rule
{
can?
(
:read_project
)
}.
enable
:read_vulnerability_feedback
...
...
ee/app/serializers/vulnerabilities/identifier_entity.rb
0 → 100644
View file @
4b35f2ac
# frozen_string_literal: true
class
Vulnerabilities::IdentifierEntity
<
Grape
::
Entity
expose
:external_type
expose
:external_id
expose
:name
expose
:url
end
ee/app/serializers/vulnerabilities/occurrence_entity.rb
0 → 100644
View file @
4b35f2ac
# frozen_string_literal: true
class
Vulnerabilities::OccurrenceEntity
<
Grape
::
Entity
include
RequestAwareEntity
expose
:id
,
:report_type
,
:name
,
:severity
,
:confidence
expose
:scanner
,
using:
Vulnerabilities
::
ScannerEntity
expose
:identifiers
,
using:
Vulnerabilities
::
IdentifierEntity
expose
:project_fingerprint
expose
:vulnerability_feedback_url
,
if:
->
(
*
)
{
can_admin_vulnerability_feedback?
}
expose
:project
,
using:
::
ProjectEntity
expose
:dismissal_feedback
,
using:
VulnerabilityFeedbackEntity
expose
:issue_feedback
,
using:
VulnerabilityFeedbackEntity
expose
:metadata
,
merge:
true
,
if:
->
(
occurrence
,
_
)
{
occurrence
.
raw_metadata
}
do
expose
:description
expose
:solution
expose
:location
expose
:links
end
alias_method
:occurrence
,
:object
private
def
vulnerability_feedback_url
project_vulnerability_feedback_index_url
(
occurrence
.
project
)
end
def
can_admin_vulnerability_feedback?
can?
(
request
.
current_user
,
:admin_vulnerability_feedback
,
occurrence
.
project
)
end
end
ee/app/serializers/vulnerabilities/occurrence_serializer.rb
0 → 100644
View file @
4b35f2ac
class
Vulnerabilities::OccurrenceSerializer
<
BaseSerializer
include
WithPagination
entity
Vulnerabilities
::
OccurrenceEntity
end
ee/app/serializers/vulnerabilities/scanner_entity.rb
0 → 100644
View file @
4b35f2ac
# frozen_string_literal: true
class
Vulnerabilities::ScannerEntity
<
Grape
::
Entity
expose
:external_id
expose
:name
end
ee/app/serializers/vulnerability_summary_entity.rb
0 → 100644
View file @
4b35f2ac
# frozen_string_literal: true
class
VulnerabilitySummaryEntity
<
Grape
::
Entity
Vulnerabilities
::
Occurrence
::
REPORT_TYPES
.
each
do
|
report_type_name
,
report_type
|
expose
report_type_name
do
Vulnerabilities
::
Occurrence
::
LEVELS
.
each
do
|
severity_name
,
severity
|
expose
severity_name
do
|
group
|
grouped_vulnerabilities
[[
report_type
,
severity
]]
||
0
end
end
end
end
private
def
grouped_vulnerabilities
@grouped_by_report_and_severity
||=
object
.
all_vulnerabilities
.
counted_by_report_and_severity
end
end
ee/app/serializers/vulnerability_summary_serializer.rb
0 → 100644
View file @
4b35f2ac
class
VulnerabilitySummarySerializer
<
BaseSerializer
entity
VulnerabilitySummaryEntity
end
ee/lib/gitlab/vulnerabilities/occurrences_preloader.rb
0 → 100644
View file @
4b35f2ac
# frozen_string_literal: true
module
Gitlab
# Preloading of Vulnerabilities Occurrences.
#
# This class can be used to efficiently preload the feedback of a given list of
# vulnerabilities (occurrences).
module
Vulnerabilities
class
OccurrencesPreloader
def
preload
(
occurrences
)
occurrences
.
each
(
&
:issue_feedback
)
occurrences
.
each
(
&
:dismissal_feedback
)
end
end
end
end
ee/spec/controllers/groups/security/vulnerabilities_controller_spec.rb
0 → 100644
View file @
4b35f2ac
require
'spec_helper'
describe
Groups
::
Security
::
VulnerabilitiesController
do
include
ApiHelpers
set
(
:group
)
{
create
(
:group
)
}
set
(
:group_other
)
{
create
(
:group
)
}
set
(
:user
)
{
create
(
:user
)
}
set
(
:project_dev
)
{
create
(
:project
,
:private
,
:repository
,
group:
group
)
}
set
(
:project_guest
)
{
create
(
:project
,
:private
,
:repository
,
group:
group
)
}
set
(
:project_other
)
{
create
(
:project
,
:public
,
:repository
,
group:
group_other
)
}
let
(
:projects
)
{
[
project_dev
,
project_guest
,
project_other
]
}
before
do
sign_in
(
user
)
end
describe
'GET index.json'
do
subject
{
get
:index
,
group_id:
group
,
format: :json
}
context
'when security dashboard feature is disabled'
do
before
do
stub_licensed_features
(
security_dashboard:
false
)
end
it
'returns 404'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
404
)
end
end
context
'when security dashboard feature is enabled'
do
before
do
stub_licensed_features
(
security_dashboard:
true
)
end
context
'when user has guest access'
do
before
do
group
.
add_guest
(
user
)
end
it
'returns 403'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
403
)
end
end
context
'when user has developer access'
do
before
do
group
.
add_developer
(
user
)
end
context
'when no page request'
do
before
do
projects
.
each
do
|
project
|
create
(
:vulnerabilities_occurrence
,
project:
project
)
end
end
it
"returns a list of vulnerabilities"
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
).
to
be_an
(
Array
)
expect
(
json_response
.
length
).
to
eq
2
expect
(
response
).
to
match_response_schema
(
'vulnerabilities/occurrence_list'
,
dir:
'ee'
)
end
end
context
'when page requested'
do
before
do
projects
.
each
do
|
project
|
create_list
(
:vulnerabilities_occurrence
,
11
,
project:
project
)
end
end
it
"returns a list of vulnerabilities"
do
get
:index
,
group_id:
group
,
page:
3
,
format: :json
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
).
to
be_an
(
Array
)
expect
(
json_response
.
length
).
to
eq
2
end
end
context
'with vulnerability feedback'
do
def
get_summary
get
:index
,
group_id:
group
,
format: :json
end
it
"avoids N+1 queries"
do
control_count
=
ActiveRecord
::
QueryRecorder
.
new
{
get_summary
}
# Create feedback
project_dev
.
vulnerabilities
.
each
do
|
occ
|
create
(
:vulnerability_feedback
,
:sast
,
:dismissal
,
project:
project_dev
,
project_fingerprint:
occ
.
project_fingerprint
)
create
(
:vulnerability_feedback
,
:sast
,
:issue
,
issue:
create
(
:issue
,
project:
project
),
project:
project_dev
,
project_fingerprint:
occ
.
project_fingerprint
)
end
expect
{
get_summary
}.
not_to
exceed_query_limit
(
control_count
)
end
end
end
end
end
describe
'GET summary.json'
do
subject
{
get
:summary
,
group_id:
group
,
format: :json
}
context
'when security dashboard feature is disabled'
do
before
do
stub_licensed_features
(
security_dashboard:
false
)
end
it
'returns 404'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
404
)
end
end
context
'when security dashboard feature is enabled'
do
before
do
stub_licensed_features
(
security_dashboard:
true
)
create_list
(
:vulnerabilities_occurrence
,
3
,
project:
project_dev
,
report_type: :sast
,
severity: :high
)
create_list
(
:vulnerabilities_occurrence
,
1
,
project:
project_dev
,
report_type: :dependency_scanning
,
severity: :low
)
create_list
(
:vulnerabilities_occurrence
,
2
,
project:
project_guest
,
report_type: :dependency_scanning
,
severity: :low
)
create_list
(
:vulnerabilities_occurrence
,
1
,
project:
project_guest
,
report_type: :dast
,
severity: :medium
)
create_list
(
:vulnerabilities_occurrence
,
1
,
project:
project_other
,
report_type: :dast
,
severity: :low
)
end
context
'when user has guest access'
do
before
do
group
.
add_guest
(
user
)
end
it
'returns 403'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
403
)
end
end
context
'when user has developer access'
do
before
do
group
.
add_developer
(
user
)
end
it
'returns vulnerabilities counts'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
).
to
be_an
(
Hash
)
expect
(
json_response
.
dig
(
'sast'
,
'high'
)).
to
eq
(
3
)
expect
(
json_response
.
dig
(
'dependency_scanning'
,
'low'
)).
to
eq
(
3
)
expect
(
json_response
.
dig
(
'dast'
,
'medium'
)).
to
eq
(
1
)
expect
(
response
).
to
match_response_schema
(
'vulnerabilities/summary'
,
dir:
'ee'
)
end
end
end
end
end
ee/spec/controllers/projects/security/dashboard_controller_spec.rb
View file @
4b35f2ac
...
...
@@ -34,10 +34,12 @@ describe Projects::Security::DashboardController do
get
:show
,
namespace_id:
project
.
namespace
,
project_id:
project
end
context
'when security reports features are enabled'
do
it
'returns the latest pipeline with security reports for project'
do
stub_licensed_features
(
sast:
true
)
context
'when security dashboard feature is enabled'
do
before
do
stub_licensed_features
(
security_dashboard:
true
)
end
it
'returns the latest pipeline with security reports for project'
do
show_security_dashboard
expect
(
response
).
to
have_gitlab_http_status
(
200
)
...
...
@@ -45,10 +47,12 @@ describe Projects::Security::DashboardController do
end
end
context
'when security reports features are disabled'
do
it
'returns the latest pipeline with security reports for project'
do
stub_licensed_features
(
sast:
false
,
dependency_scanning:
false
,
sast_container:
false
,
dast:
false
)
context
'when security dashboard feature is disabled'
do
before
do
stub_licensed_features
(
security_dashboard:
false
)
end
it
'returns 404'
do
show_security_dashboard
expect
(
response
).
to
have_gitlab_http_status
(
404
)
...
...
@@ -59,9 +63,11 @@ describe Projects::Security::DashboardController do
context
'with unauthorized user for security dashboard'
do
let
(
:guest
)
{
create
(
:user
)
}
it
'returns a not found 404 response'
do
stub_licensed_features
(
sast:
true
)
before
do
stub_licensed_features
(
security_dashboard:
true
)
end
it
'returns a not found 404 response'
do
group
.
add_guest
(
guest
)
show_security_dashboard
guest
...
...
ee/spec/factories/vulnerabilities/occurrences.rb
View file @
4b35f2ac
# frozen_string_literal: true
FactoryBot
.
define
do
sequence
:vulnerability_occurrence_uuid
do
|
n
|
Digest
::
SHA1
.
hexdigest
(
"uuid-
#{
n
}
"
)[
0
..
35
]
end
factory
:vulnerabilities_occurrence
,
class:
Vulnerabilities
::
Occurrence
do
name
'Cipher with no integrity'
project
pipeline
factory: :ci_pipeline
ref
'master'
uuid
'a7342ca9-494e-457f-88e7-e65e145cc392'
project_fingerprint
'4e5b6966dd100170b4b1ad599c7058cce91b57b4'
sequence
(
:uuid
)
{
generate
(
:vulnerability_occurrence_uuid
)
}
project_fingerprint
{
generate
(
:project_fingerprint
)
}
primary_identifier_fingerprint
'4e5b6966dd100170b4b1ad599c7058cce91b57b4'
location_fingerprint
'4e5b6966dd100170b4b1ad599c7058cce91b57b4'
report_type
:sast
...
...
@@ -15,6 +19,24 @@ FactoryBot.define do
confidence
:medium
scanner
factory: :vulnerabilities_scanner
metadata_version
'sast:1.0'
raw_metadata
'raw_metadata'
raw_metadata
do
{
description:
"The cipher does not provide data integrity update 1"
,
solution:
"GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result."
,
location:
{
file:
"maven/src/main/java/com/gitlab/security_products/tests/App.java"
,
start_line:
29
,
end_line:
29
,
class:
"com.gitlab.security_products.tests.App"
,
method:
"insecureCypher"
},
links:
[
{
name:
"Cipher does not check for integrity first?"
,
url:
"https://crypto.stackexchange.com/questions/31428/pbewithmd5anddes-cipher-does-not-check-for-integrity-first"
}
]
}.
to_json
end
end
end
ee/spec/fixtures/api/schemas/vulnerabilities/occurrence.json
0 → 100644
View file @
4b35f2ac
{
"type"
:
"object"
,
"required"
:
[
"name"
,
"project_fingerprint"
,
"confidence"
,
"severity"
,
"report_type"
,
"scanner"
,
"project"
],
"properties"
:
{
"name"
:
{
"type"
:
"string"
},
"project_fingerprint"
:
{
"type"
:
"string"
},
"vulnerability_feedback_url"
:
{
"type"
:
"string"
},
"confidence"
:
{
"type"
:
"string"
,
"enum"
:
[
"undefined"
,
"ignore"
,
"unknown"
,
"experimental"
,
"low"
,
"medium"
,
"high"
,
"critical"
]
},
"severity"
:
{
"type"
:
"string"
,
"enum"
:
[
"undefined"
,
"ignore"
,
"unknown"
,
"experimental"
,
"low"
,
"medium"
,
"high"
,
"critical"
]
},
"report_type"
:
{
"type"
:
"string"
,
"enum"
:
[
"sast"
,
"dependency_scanning"
,
"container_scanning"
,
"dast"
]
},
"scanner"
:
{
"external_id"
:
{
"type"
:
"string"
},
"name"
:
{
"type"
:
"string"
}
},
"project"
:
{
"required"
:
[
"id"
,
"name"
,
"full_path"
,
"full_name"
],
"id"
:
{
"type"
:
"integer"
},
"name"
:
{
"type"
:
"string"
},
"full_path"
:
{
"type"
:
"string"
},
"full_name"
:
{
"type"
:
"string"
}
},
"issue_feedback"
:
{
"oneOf"
:
[
{
"type"
:
"null"
},
{
"$ref"
:
"../vulnerability_feedback.json"
}
]},
"dismissal_feedback"
:
{
"oneOf"
:
[
{
"type"
:
"null"
},
{
"$ref"
:
"../vulnerability_feedback.json"
}
]},
"description"
:
{
"type"
:
"string"
},
"solution"
:
{
"type"
:
"string"
},
"location"
:
{
"class"
:
{
"type"
:
"string"
},
"method"
:
{
"type"
:
"string"
},
"file"
:
{
"type"
:
"string"
},
"start_line"
:
{
"type"
:
"integer"
},
"end_line"
:
{
"type"
:
"integer"
}
},
"links"
:
{
"type"
:
"array"
,
"items"
:
{
"name"
:
{
"type"
:
[
"string"
,
"null"
]
},
"url"
:
{
"type"
:
[
"string"
,
"null"
]
}
}
},
"identifiers"
:
{
"type"
:
"array"
,
"items"
:
{
"primary"
:
{
"type"
:
[
"boolean"
]
},
"name"
:
{
"type"
:
[
"string"
]
},
"url"
:
{
"type"
:
[
"string"
,
"null"
]
},
"external_id"
:
{
"type"
:
[
"string"
]
},
"external_type"
:
{
"type"
:
[
"string"
]
}
}
}
}
}
ee/spec/fixtures/api/schemas/vulnerabilities/occurrence_list.json
0 → 100644
View file @
4b35f2ac
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"occurrence.json"
}
}
ee/spec/fixtures/api/schemas/vulnerabilities/summary.json
0 → 100644
View file @
4b35f2ac
{
"type"
:
"object"
,
"required"
:
[
"dast"
,
"sast"
,
"container_scanning"
,
"dependency_scanning"
],
"properties"
:
{
"dast"
:
{
"$ref"
:
"summary_for_report_type.json"
},
"sast"
:
{
"$ref"
:
"summary_for_report_type.json"
},
"container_scanning"
:
{
"$ref"
:
"summary_for_report_type.json"
},
"dependency_scanning"
:
{
"$ref"
:
"summary_for_report_type.json"
}
},
"additional_properties"
:
false
}
ee/spec/fixtures/api/schemas/vulnerabilities/summary_for_report_type.json
0 → 100644
View file @
4b35f2ac
{
"type"
:
"object"
,
"properties"
:
{
"undefined"
:
{
"type"
:
"integer"
},
"ignore"
:
{
"type"
:
"integer"
},
"unknown"
:
{
"type"
:
"integer"
},
"experimental"
:
{
"type"
:
"integer"
},
"low"
:
{
"type"
:
"integer"
},
"medium"
:
{
"type"
:
"integer"
},
"high"
:
{
"type"
:
"integer"
},
"critical"
:
{
"type"
:
"integer"
}
},
"additional_properties"
:
false
}
ee/spec/models/project_spec.rb
View file @
4b35f2ac
...
...
@@ -1526,29 +1526,6 @@ describe Project do
end
end
describe
'#security_reports_feature_available?'
do
security_features
=
%i[sast dependency_scanning sast_container dast]
let
(
:project
)
{
create
(
:project
)
}
security_features
.
each
do
|
feature
|
it
"returns true when at least
#{
feature
}
is enabled"
do
allow
(
project
).
to
receive
(
:feature_available?
)
{
false
}
allow
(
project
).
to
receive
(
:feature_available?
).
with
(
feature
)
{
true
}
expect
(
project
.
security_reports_feature_available?
).
to
eq
(
true
)
end
end
it
"returns false when all security features are disabled"
do
security_features
.
each
do
|
feature
|
allow
(
project
).
to
receive
(
:feature_available?
).
with
(
feature
)
{
false
}
end
expect
(
project
.
security_reports_feature_available?
).
to
eq
(
false
)
end
end
describe
'#latest_pipeline_with_security_reports'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:pipeline_1
)
{
create
(
:ci_pipeline_without_jobs
,
project:
project
)
}
...
...
ee/spec/policies/group_policy_spec.rb
View file @
4b35f2ac
...
...
@@ -400,4 +400,68 @@ describe GroupPolicy do
end
end
end
describe
'read_group_security_dashboard'
do
before
do
stub_licensed_features
(
security_dashboard:
true
)
end
subject
{
described_class
.
new
(
current_user
,
group
)
}
context
'with admin'
do
let
(
:current_user
)
{
admin
}
it
{
is_expected
.
to
be_allowed
(
:read_group_security_dashboard
)
}
end
context
'with owner'
do
let
(
:current_user
)
{
owner
}
it
{
is_expected
.
to
be_allowed
(
:read_group_security_dashboard
)
}
end
context
'with maintainer'
do
let
(
:current_user
)
{
maintainer
}
it
{
is_expected
.
to
be_allowed
(
:read_group_security_dashboard
)
}
end
context
'with developer'
do
let
(
:current_user
)
{
developer
}
it
{
is_expected
.
to
be_allowed
(
:read_group_security_dashboard
)
}
context
'when security dashboard features is not available'
do
before
do
stub_licensed_features
(
security_dashboard:
false
)
end
it
{
is_expected
.
to
be_disallowed
(
:read_group_security_dashboard
)
}
end
end
context
'with reporter'
do
let
(
:current_user
)
{
reporter
}
it
{
is_expected
.
to
be_disallowed
(
:read_group_security_dashboard
)
}
end
context
'with guest'
do
let
(
:current_user
)
{
guest
}
it
{
is_expected
.
to
be_disallowed
(
:read_group_security_dashboard
)
}
end
context
'with non member'
do
let
(
:current_user
)
{
create
(
:user
)
}
it
{
is_expected
.
to
be_disallowed
(
:read_group_security_dashboard
)
}
end
context
'with anonymous'
do
let
(
:current_user
)
{
nil
}
it
{
is_expected
.
to
be_disallowed
(
:read_group_security_dashboard
)
}
end
end
end
ee/spec/policies/project_policy_spec.rb
View file @
4b35f2ac
...
...
@@ -306,7 +306,7 @@ describe ProjectPolicy do
describe
'read_project_security_dashboard'
do
before
do
allow
(
project
).
to
receive
(
:security_reports_feature_available?
).
and_return
(
true
)
stub_licensed_features
(
security_dashboard:
true
)
end
subject
{
described_class
.
new
(
current_user
,
project
)
}
...
...
@@ -334,9 +334,9 @@ describe ProjectPolicy do
it
{
is_expected
.
to
be_allowed
(
:read_project_security_dashboard
)
}
context
'when security
reports features are
not available'
do
context
'when security
dashboard features is
not available'
do
before
do
allow
(
project
).
to
receive
(
:security_reports_feature_available?
).
and_return
(
false
)
stub_licensed_features
(
security_dashboard:
false
)
end
it
{
is_expected
.
to
be_disallowed
(
:read_project_security_dashboard
)
}
...
...
ee/spec/serializers/vulnerabilities/identifier_entity_spec.rb
0 → 100644
View file @
4b35f2ac
require
'spec_helper'
describe
Vulnerabilities
::
IdentifierEntity
do
let
(
:identifier
)
{
create
(
:vulnerabilities_identifier
)
}
let
(
:entity
)
do
described_class
.
represent
(
identifier
)
end
describe
'#as_json'
do
subject
{
entity
.
as_json
}
it
'contains required fields'
do
expect
(
subject
).
to
include
(
:external_type
,
:external_id
,
:name
,
:url
)
end
end
end
ee/spec/serializers/vulnerabilities/occurrence_entity_spec.rb
0 → 100644
View file @
4b35f2ac
require
'spec_helper'
describe
Vulnerabilities
::
OccurrenceEntity
do
set
(
:user
)
{
create
(
:user
)
}
set
(
:project
)
{
create
(
:project
)
}
let
(
:scanner
)
do
create
(
:vulnerabilities_scanner
,
project:
project
)
end
let
(
:identifiers
)
do
[
create
(
:vulnerabilities_identifier
),
create
(
:vulnerabilities_identifier
)
]
end
let
(
:occurrence
)
do
create
(
:vulnerabilities_occurrence
,
scanner:
scanner
,
project:
project
,
identifiers:
identifiers
)
end
let!
(
:dismiss_feedback
)
do
create
(
:vulnerability_feedback
,
:sast
,
:dismissal
,
project:
project
,
project_fingerprint:
occurrence
.
project_fingerprint
)
end
let!
(
:issue_feedback
)
do
create
(
:vulnerability_feedback
,
:sast
,
:issue
,
issue:
create
(
:issue
,
project:
project
),
project:
project
,
project_fingerprint:
occurrence
.
project_fingerprint
)
end
let
(
:request
)
{
double
(
'request'
)
}
let
(
:entity
)
do
described_class
.
represent
(
occurrence
,
request:
request
)
end
describe
'#as_json'
do
subject
{
entity
.
as_json
}
before
do
allow
(
request
).
to
receive
(
:current_user
).
and_return
(
user
)
end
it
'contains required fields'
do
expect
(
subject
).
to
include
(
:id
)
expect
(
subject
).
to
include
(
:name
,
:report_type
,
:severity
,
:confidence
,
:project_fingerprint
)
expect
(
subject
).
to
include
(
:scanner
,
:project
,
:identifiers
)
expect
(
subject
).
to
include
(
:dismissal_feedback
,
:issue_feedback
)
expect
(
subject
).
to
include
(
:description
,
:solution
,
:location
,
:links
)
end
context
'when not allowed to admin vulnerability feedback'
do
before
do
project
.
add_guest
(
user
)
end
it
'does not contain vulnerability feedback URL'
do
expect
(
subject
).
not_to
include
(
:vulnerability_feedback_url
)
end
end
context
'when allowed to admin vulnerability feedback'
do
before
do
project
.
add_developer
(
user
)
end
it
'contains vulnerability feedback URL'
do
expect
(
subject
).
to
include
(
:vulnerability_feedback_url
)
end
end
end
end
ee/spec/serializers/vulnerabilities/scanner_entity_spec.rb
0 → 100644
View file @
4b35f2ac
require
'spec_helper'
describe
Vulnerabilities
::
ScannerEntity
do
let
(
:scanner
)
{
create
(
:vulnerabilities_scanner
)
}
let
(
:entity
)
do
described_class
.
represent
(
scanner
)
end
describe
'#as_json'
do
subject
{
entity
.
as_json
}
it
'contains required fields'
do
expect
(
subject
).
to
include
(
:name
,
:external_id
)
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