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
27e752d2
Commit
27e752d2
authored
Dec 03, 2021
by
Daniel Tian
Committed by
Savas Vedova
Dec 03, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add new project vulnerability report
parent
2ba756c4
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
335 additions
and
8 deletions
+335
-8
ee/app/assets/javascripts/security_dashboard/components/project/project_vulnerability_report.vue
...board/components/project/project_vulnerability_report.vue
+132
-0
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list_graphql.vue
...hared/vulnerability_report/vulnerability_list_graphql.vue
+2
-1
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_report_tabs.vue
...shared/vulnerability_report/vulnerability_report_tabs.vue
+4
-0
ee/app/assets/javascripts/security_dashboard/graphql/queries/instance_vulnerabilities.query.graphql
...rd/graphql/queries/instance_vulnerabilities.query.graphql
+3
-3
ee/app/assets/javascripts/security_dashboard/graphql/queries/project_vulnerabilities.query.graphql
...ard/graphql/queries/project_vulnerabilities.query.graphql
+3
-3
ee/app/assets/javascripts/security_dashboard/vulnerability_report_init.js
...vascripts/security_dashboard/vulnerability_report_init.js
+3
-0
ee/spec/frontend/security_dashboard/components/instance/instance_vulnerabilities_spec.js
...oard/components/instance/instance_vulnerabilities_spec.js
+1
-1
ee/spec/frontend/security_dashboard/components/project/project_vulnerability_report_spec.js
...d/components/project/project_vulnerability_report_spec.js
+172
-0
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_graphql_spec.js
...d/vulnerability_report/vulnerability_list_graphql_spec.js
+15
-0
No files found.
ee/app/assets/javascripts/security_dashboard/components/project/project_vulnerability_report.vue
0 → 100644
View file @
27e752d2
<
script
>
import
Cookies
from
'
js-cookie
'
;
import
{
difference
}
from
'
lodash
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
LocalStorageSync
from
'
~/vue_shared/components/local_storage_sync.vue
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
{
API_FUZZING_NAME
,
CLUSTER_IMAGE_SCANNING_NAME
,
CONTAINER_SCANNING_NAME
,
COVERAGE_FUZZING_NAME
,
DEPENDENCY_SCANNING_NAME
,
SECRET_DETECTION_NAME
,
}
from
'
~/security_configuration/components/constants
'
;
import
ReportNotConfiguredProject
from
'
../shared/empty_states/report_not_configured_project.vue
'
;
import
VulnerabilityReportTabs
from
'
../shared/vulnerability_report/vulnerability_report_tabs.vue
'
;
import
projectVulnerabilitiesQuery
from
'
../../graphql/queries/project_vulnerabilities.query.graphql
'
;
import
AutoFixUserCallout
from
'
../shared/auto_fix_user_callout.vue
'
;
import
ProjectPipelineStatus
from
'
../shared/project_pipeline_status.vue
'
;
import
securityScannersQuery
from
'
../../graphql/queries/project_security_scanners.query.graphql
'
;
import
SecurityScannerAlert
from
'
./security_scanner_alert.vue
'
;
export
default
{
components
:
{
ReportNotConfiguredProject
,
LocalStorageSync
,
SecurityScannerAlert
,
AutoFixUserCallout
,
ProjectPipelineStatus
,
VulnerabilityReportTabs
,
},
mixins
:
[
glFeatureFlagsMixin
()],
inject
:
[
'
fullPath
'
,
'
pipeline
'
,
'
autoFixDocumentation
'
],
data
()
{
return
{
scannerAlertDismissed
:
false
,
securityScanners
:
{},
shouldShowAutoFixUserCallout
:
this
.
glFeatures
.
securityAutoFix
&&
!
Cookies
.
get
(
this
.
$options
.
autoFixUserCalloutCookieName
),
};
},
apollo
:
{
securityScanners
:
{
query
:
securityScannersQuery
,
variables
()
{
return
{
fullPath
:
this
.
fullPath
};
},
update
({
project
=
{}
})
{
const
{
available
=
[],
enabled
=
[],
pipelineRun
=
[]
}
=
project
?.
securityScanners
||
{};
const
translateScannerName
=
(
scannerName
)
=>
this
.
$options
.
i18n
[
scannerName
]
||
scannerName
;
return
{
available
:
available
.
map
(
translateScannerName
),
enabled
:
enabled
.
map
(
translateScannerName
),
pipelineRun
:
pipelineRun
.
map
(
translateScannerName
),
};
},
},
},
computed
:
{
isReportConfigured
()
{
return
this
.
pipeline
?.
id
;
},
notEnabledSecurityScanners
()
{
const
{
available
=
[],
enabled
=
[]
}
=
this
.
securityScanners
;
return
difference
(
available
,
enabled
);
},
noPipelineRunSecurityScanners
()
{
const
{
enabled
=
[],
pipelineRun
=
[]
}
=
this
.
securityScanners
;
return
difference
(
enabled
,
pipelineRun
);
},
shouldShowScannersAlert
()
{
return
(
!
this
.
scannerAlertDismissed
&&
(
this
.
notEnabledSecurityScanners
.
length
>
0
||
this
.
noPipelineRunSecurityScanners
.
length
>
0
)
);
},
},
methods
:
{
closeAutoFixUserCallout
()
{
Cookies
.
set
(
this
.
$options
.
autoFixUserCalloutCookieName
,
'
true
'
);
this
.
shouldShowAutoFixUserCallout
=
false
;
},
setScannerAlertDismissed
(
value
)
{
this
.
scannerAlertDismissed
=
parseBoolean
(
value
);
},
},
projectVulnerabilitiesQuery
,
autoFixUserCalloutCookieName
:
'
auto_fix_user_callout_dismissed
'
,
SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY
:
'
vulnerability_list_scanner_alert_dismissed
'
,
i18n
:
{
API_FUZZING
:
API_FUZZING_NAME
,
CONTAINER_SCANNING
:
CONTAINER_SCANNING_NAME
,
CLUSTER_IMAGE_SCANNING
:
CLUSTER_IMAGE_SCANNING_NAME
,
COVERAGE_FUZZING
:
COVERAGE_FUZZING_NAME
,
SECRET_DETECTION
:
SECRET_DETECTION_NAME
,
DEPENDENCY_SCANNING
:
DEPENDENCY_SCANNING_NAME
,
},
};
</
script
>
<
template
>
<report-not-configured-project
v-if=
"!isReportConfigured"
/>
<div
v-else
>
<local-storage-sync
:value=
"String(scannerAlertDismissed)"
:storage-key=
"$options.SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY"
@
input=
"setScannerAlertDismissed"
/>
<security-scanner-alert
v-if=
"shouldShowScannersAlert"
:not-enabled-scanners=
"notEnabledSecurityScanners"
:no-pipeline-run-scanners=
"noPipelineRunSecurityScanners"
@
dismiss=
"setScannerAlertDismissed('true')"
/>
<auto-fix-user-callout
v-if=
"shouldShowAutoFixUserCallout"
:help-page-path=
"autoFixDocumentation"
@
close=
"closeAutoFixUserCallout"
/>
<vulnerability-report-tabs
:query=
"$options.projectVulnerabilitiesQuery"
>
<template
#header-development
>
<project-pipeline-status
:pipeline=
"pipeline"
/>
</
template
>
</vulnerability-report-tabs>
</div>
</template>
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list_graphql.vue
View file @
27e752d2
...
...
@@ -20,7 +20,7 @@ const deepFindVulnerabilities = (data) => {
export
default
{
components
:
{
GlLoadingIcon
,
GlIntersectionObserver
,
VulnerabilityList
},
inject
:
[
'
fullPath
'
,
'
canViewFalsePositive
'
],
inject
:
[
'
fullPath
'
,
'
canViewFalsePositive
'
,
'
hasJiraVulnerabilitiesIntegrationEnabled
'
],
props
:
{
query
:
{
type
:
Object
,
...
...
@@ -59,6 +59,7 @@ export default {
fullPath
:
this
.
fullPath
,
sort
:
this
.
sort
,
vetEnabled
:
this
.
canViewFalsePositive
,
includeExternalIssueLinks
:
this
.
hasJiraVulnerabilitiesIntegrationEnabled
,
...
this
.
filters
,
};
},
...
...
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_report_tabs.vue
View file @
27e752d2
...
...
@@ -46,6 +46,8 @@ export default {
<gl-tabs
class=
"gl-mt-5"
content-class=
"gl-pt-0"
sync-active-tab-with-query-params
>
<gl-tab
:title=
"$options.i18n.developmentTab"
lazy
>
<slot
name=
"header-development"
></slot>
<vulnerability-report
:type=
"$options.REPORT_TAB.DEVELOPMENT"
:query=
"query"
...
...
@@ -60,6 +62,8 @@ export default {
>
<gl-card
body-class=
"gl-p-6"
>
{{
$options
.
i18n
.
operationalTabMessage
}}
</gl-card>
<slot
name=
"header-operational"
></slot>
<vulnerability-report
:type=
"$options.REPORT_TAB.OPERATIONAL"
:query=
"query"
...
...
ee/app/assets/javascripts/security_dashboard/graphql/queries/instance_vulnerabilities.query.graphql
View file @
27e752d2
#import "~/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql"
#import "../fragments/vulnerability.fragment.graphql"
query
instanceVulnerabilities
(
$after
:
String
$first
:
Int
$first
:
Int
=
20
$projectId
:
[
ID
!]
$severity
:
[
VulnerabilitySeverity
!]
$reportType
:
[
VulnerabilityReportType
!]
...
...
@@ -32,7 +31,8 @@ query instanceVulnerabilities(
...
VulnerabilityFragment
}
pageInfo
{
...
PageInfo
endCursor
hasNextPage
}
}
}
ee/app/assets/javascripts/security_dashboard/graphql/queries/project_vulnerabilities.query.graphql
View file @
27e752d2
#import "~/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql"
#import "../fragments/vulnerability.fragment.graphql"
query
projectVulnerabilities
(
$fullPath
:
ID
!
$after
:
String
$first
:
Int
$first
:
Int
=
20
$severity
:
[
VulnerabilitySeverity
!]
$reportType
:
[
VulnerabilityReportType
!]
$scanner
:
[
String
!]
...
...
@@ -53,7 +52,8 @@ query projectVulnerabilities(
}
}
pageInfo
{
...
PageInfo
endCursor
hasNextPage
}
}
}
...
...
ee/app/assets/javascripts/security_dashboard/vulnerability_report_init.js
View file @
27e752d2
...
...
@@ -6,11 +6,14 @@ import VulnerabilityReport from './components/shared/vulnerability_report.vue';
import
apolloProvider
from
'
./graphql/provider
'
;
import
createRouter
from
'
./router
'
;
import
createStore
from
'
./store
'
;
import
ProjectVulnerabilityReport
from
'
./components/project/project_vulnerability_report.vue
'
;
import
GroupVulnerabilityReport
from
'
./components/group/group_vulnerability_report.vue
'
;
import
InstanceVulnerabilityReport
from
'
./components/instance/instance_vulnerability_report.vue
'
;
const
getVulnerabilityComponent
=
(
dashboardType
)
=>
{
switch
(
dashboardType
)
{
case
DASHBOARD_TYPES
.
PROJECT
:
return
ProjectVulnerabilityReport
;
case
DASHBOARD_TYPES
.
GROUP
:
return
GroupVulnerabilityReport
;
case
DASHBOARD_TYPES
.
INSTANCE
:
...
...
ee/spec/frontend/security_dashboard/components/instance/instance_vulnerabilities_spec.js
View file @
27e752d2
...
...
@@ -197,7 +197,7 @@ describe('Instance Security Dashboard Vulnerabilities Component', () => {
data
:
{
vulnerabilities
:
{
nodes
:
[],
pageInfo
:
{
startCursor
:
''
,
endCursor
:
''
},
pageInfo
:
{
endCursor
:
''
,
hasNextPage
:
false
},
},
},
});
...
...
ee/spec/frontend/security_dashboard/components/project/project_vulnerability_report_spec.js
0 → 100644
View file @
27e752d2
import
{
nextTick
}
from
'
vue
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
Cookies
from
'
js-cookie
'
;
import
ProjectVulnerabilityReport
from
'
ee/security_dashboard/components/project/project_vulnerability_report.vue
'
;
import
ReportNotConfiguredProject
from
'
ee/security_dashboard/components/shared/empty_states/report_not_configured_project.vue
'
;
import
VulnerabilityReportTabs
from
'
ee/security_dashboard/components/shared/vulnerability_report/vulnerability_report_tabs.vue
'
;
import
ProjectPipelineStatus
from
'
ee/security_dashboard/components/shared/project_pipeline_status.vue
'
;
import
SecurityScannerAlert
from
'
ee/security_dashboard/components/project/security_scanner_alert.vue
'
;
import
securityScannersQuery
from
'
ee/security_dashboard/graphql/queries/project_security_scanners.query.graphql
'
;
import
AutoFixUserCallout
from
'
ee/security_dashboard/components/shared/auto_fix_user_callout.vue
'
;
import
LocalStorageSync
from
'
~/vue_shared/components/local_storage_sync.vue
'
;
import
{
useLocalStorageSpy
}
from
'
helpers/local_storage_helper
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
describe
(
'
Project vulnerability report app component
'
,
()
=>
{
useLocalStorageSpy
();
let
wrapper
;
const
securityScannersHandler
=
({
available
=
[],
enabled
=
[],
pipelineRun
=
[]
}
=
{})
=>
jest
.
fn
().
mockResolvedValue
({
data
:
{
project
:
{
id
:
1
,
securityScanners
:
{
available
,
enabled
,
pipelineRun
},
},
},
});
const
createWrapper
=
({
pipeline
=
{
id
:
1
},
securityScanners
,
securityAutoFix
=
false
,
}
=
{})
=>
{
wrapper
=
shallowMount
(
ProjectVulnerabilityReport
,
{
localVue
,
apolloProvider
:
createMockApollo
([
[
securityScannersQuery
,
securityScannersHandler
(
securityScanners
)],
]),
provide
:
{
fullPath
:
'
#
'
,
autoFixDocumentation
:
'
#
'
,
pipeline
,
glFeatures
:
{
securityAutoFix
},
},
stubs
:
{
VulnerabilityReportTabs
,
LocalStorageSync
,
},
});
};
const
findReportNotConfiguredProject
=
()
=>
wrapper
.
find
(
ReportNotConfiguredProject
);
const
findVulnerabilityReportTabs
=
()
=>
wrapper
.
findComponent
(
VulnerabilityReportTabs
);
const
findAutoFixUserCallout
=
()
=>
wrapper
.
findComponent
(
AutoFixUserCallout
);
const
findProjectPipelineStatus
=
()
=>
wrapper
.
findComponent
(
ProjectPipelineStatus
);
const
findSecurityScannerAlert
=
(
root
=
wrapper
)
=>
root
.
findComponent
(
SecurityScannerAlert
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
report not configured component
'
,
()
=>
{
it
(
'
shows the report not configured component if there are no projects
'
,
()
=>
{
createWrapper
({
pipeline
:
null
});
expect
(
findReportNotConfiguredProject
().
exists
()).
toBe
(
true
);
expect
(
findVulnerabilityReportTabs
().
exists
()).
toBe
(
false
);
});
it
(
'
shows the vulnerability report tabs and project pipeline status components if there are projects
'
,
()
=>
{
const
pipeline
=
{
id
:
1
};
createWrapper
({
pipeline
});
expect
(
findReportNotConfiguredProject
().
exists
()).
toBe
(
false
);
expect
(
findVulnerabilityReportTabs
().
exists
()).
toBe
(
true
);
expect
(
findProjectPipelineStatus
().
props
(
'
pipeline
'
)).
toBe
(
pipeline
);
});
});
describe
(
'
security scanner alerts component
'
,
()
=>
{
describe
.
each
`
available | enabled | pipelineRun | expectAlertShown
${[
'
DAST
'
]}
|
${[]}
|
${[]}
|
${
true
}
${[
'
DAST
'
]}
|
${[
'
DAST
'
]}
|
${[]}
|
${
true
}
${[
'
DAST
'
]}
|
${[]}
|
${[
'
DAST
'
]}
|
${
true
}
${[
'
DAST
'
]}
|
${[
'
DAST
'
]}
|
${[
'
DAST
'
]}
|
${
false
}
${[]}
|
${[]}
|
${[]}
|
${
false
}
`
(
'
visibility
'
,
({
available
,
enabled
,
pipelineRun
,
expectAlertShown
})
=>
{
it
(
`should
${
expectAlertShown
?
''
:
'
not
'
}
show the alert`
,
async
()
=>
{
createWrapper
({
securityScanners
:
{
available
,
enabled
,
pipelineRun
}
});
await
nextTick
();
expect
(
findSecurityScannerAlert
().
exists
()).
toBe
(
expectAlertShown
);
});
it
(
'
should never show the alert once it has been dismissed
'
,
()
=>
{
window
.
localStorage
.
setItem
(
ProjectVulnerabilityReport
.
SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY
,
'
true
'
,
);
createWrapper
({
securityScanners
:
{
available
,
enabled
,
pipelineRun
}
});
expect
(
findSecurityScannerAlert
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
dismissal
'
,
()
=>
{
beforeEach
(()
=>
{
createWrapper
({
securityScanners
:
{
available
:
[
'
DAST
'
],
enabled
:
[],
pipelineRun
:
[]
},
});
});
it
(
'
should hide the alert when it is dismissed
'
,
async
()
=>
{
const
scannerAlert
=
findSecurityScannerAlert
();
expect
(
scannerAlert
.
exists
()).
toBe
(
true
);
scannerAlert
.
vm
.
$emit
(
'
dismiss
'
);
await
nextTick
();
expect
(
scannerAlert
.
exists
()).
toBe
(
false
);
});
it
(
'
should remember the dismissal state
'
,
async
()
=>
{
findSecurityScannerAlert
().
vm
.
$emit
(
'
dismiss
'
);
await
nextTick
();
expect
(
window
.
localStorage
.
setItem
).
toHaveBeenCalledWith
(
ProjectVulnerabilityReport
.
SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY
,
'
true
'
,
);
});
});
});
describe
(
'
auto fix user callout component
'
,
()
=>
{
it
(
'
does not show user callout when feature flag is disabled
'
,
()
=>
{
createWrapper
({
securityAutoFix
:
false
});
expect
(
findAutoFixUserCallout
().
exists
()).
toBe
(
false
);
});
it
(
'
shows user callout when the cookie is not set and hides it when dismissed
'
,
async
()
=>
{
jest
.
spyOn
(
Cookies
,
'
set
'
);
createWrapper
({
securityAutoFix
:
true
});
const
autoFixUserCallOut
=
findAutoFixUserCallout
();
expect
(
autoFixUserCallOut
.
exists
()).
toBe
(
true
);
autoFixUserCallOut
.
vm
.
$emit
(
'
close
'
);
await
nextTick
();
expect
(
autoFixUserCallOut
.
exists
()).
toBe
(
false
);
expect
(
Cookies
.
set
).
toHaveBeenCalledWith
(
wrapper
.
vm
.
$options
.
autoFixUserCalloutCookieName
,
'
true
'
,
);
});
it
(
'
does not show user callout when the cookie is set
'
,
()
=>
{
jest
.
doMock
(
'
js-cookie
'
,
()
=>
({
get
:
jest
.
fn
().
mockReturnValue
(
true
)
}));
createWrapper
({
securityAutoFix
:
true
});
expect
(
findAutoFixUserCallout
().
exists
()).
toBe
(
false
);
});
});
});
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_graphql_spec.js
View file @
27e752d2
...
...
@@ -38,6 +38,7 @@ describe('Vulnerability list GraphQL component', () => {
vulnerabilitiesHandler
=
vulnerabilitiesRequestHandler
,
canViewFalsePositive
=
false
,
showProjectNamespace
=
false
,
hasJiraVulnerabilitiesIntegrationEnabled
=
false
,
filters
=
{},
fields
=
[],
}
=
{})
=>
{
...
...
@@ -47,6 +48,7 @@ describe('Vulnerability list GraphQL component', () => {
provide
:
{
fullPath
,
canViewFalsePositive
,
hasJiraVulnerabilitiesIntegrationEnabled
,
},
propsData
:
{
query
:
vulnerabilitiesQuery
,
...
...
@@ -86,6 +88,19 @@ describe('Vulnerability list GraphQL component', () => {
},
);
it
.
each
([
true
,
false
])(
'
calls the query with the expected includeExternalIssueLinks property when hasJiraVulnerabilitiesIntegrationEnabled is %s
'
,
(
hasJiraVulnerabilitiesIntegrationEnabled
)
=>
{
createWrapper
({
hasJiraVulnerabilitiesIntegrationEnabled
});
expect
(
vulnerabilitiesRequestHandler
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
includeExternalIssueLinks
:
hasJiraVulnerabilitiesIntegrationEnabled
,
}),
);
},
);
it
(
'
does not call the query if filters are not ready
'
,
()
=>
{
createWrapper
({
filters
:
null
});
...
...
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