Commit 773314a4 authored by Philippe Lafoucrière's avatar Philippe Lafoucrière

Merge branch '8150_split_vulnerability_feedback_urls' into 'master'

Group Security Dashboard: Split vulnerability feedback urls

See merge request gitlab-org/gitlab-ee!9123
parents 35279263 d28090eb
<script> <script>
import _ from 'underscore';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue'; import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import Filters from './filters.vue'; import Filters from './filters.vue';
...@@ -49,6 +50,14 @@ export default { ...@@ -49,6 +50,14 @@ export default {
...mapState('vulnerabilities', ['modal']), ...mapState('vulnerabilities', ['modal']),
...mapState('projects', ['projects']), ...mapState('projects', ['projects']),
...mapGetters('filters', ['activeFilters']), ...mapGetters('filters', ['activeFilters']),
canCreateIssuePermission() {
const path = this.modal.vulnerability.vulnerability_feedback_issue_path;
return _.isString(path) && !_.isEmpty(path);
},
canCreateFeedbackPermission() {
const path = this.modal.vulnerability.vulnerability_feedback_dismissal_path;
return _.isString(path) && !_.isEmpty(path);
},
}, },
created() { created() {
this.setProjectsEndpoint(this.projectsEndpoint); this.setProjectsEndpoint(this.projectsEndpoint);
...@@ -94,8 +103,8 @@ export default { ...@@ -94,8 +103,8 @@ export default {
<issue-modal <issue-modal
:modal="modal" :modal="modal"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath" :vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
:can-create-issue-permission="true" :can-create-issue-permission="canCreateIssuePermission"
:can-create-feedback-permission="true" :can-create-feedback-permission="canCreateFeedbackPermission"
@createNewIssue="createIssue({ vulnerability: modal.vulnerability })" @createNewIssue="createIssue({ vulnerability: modal.vulnerability })"
@dismissIssue="dismissVulnerability({ vulnerability: modal.vulnerability })" @dismissIssue="dismissVulnerability({ vulnerability: modal.vulnerability })"
@revertDismissIssue="revertDismissal({ vulnerability: modal.vulnerability })" @revertDismissIssue="revertDismissal({ vulnerability: modal.vulnerability })"
......
<script> <script>
import _ from 'underscore';
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { GlButton, GlSkeletonLoading } from '@gitlab/ui'; import { GlButton, GlSkeletonLoading } from '@gitlab/ui';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue'; import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
...@@ -44,10 +45,12 @@ export default { ...@@ -44,10 +45,12 @@ export default {
return Boolean(this.vulnerability.issue_feedback); return Boolean(this.vulnerability.issue_feedback);
}, },
canDismissVulnerability() { canDismissVulnerability() {
return Boolean(this.vulnerability.vulnerability_feedback_url); const path = this.vulnerability.vulnerability_feedback_dismissal_path;
return _.isString(path) && !_.isEmpty(path);
}, },
canCreateIssue() { canCreateIssue() {
return this.canDismissVulnerability && !this.hasIssue; const path = this.vulnerability.vulnerability_feedback_issue_path;
return _.isString(path) && !_.isEmpty(path) && !this.hasIssue;
}, },
}, },
methods: { methods: {
......
...@@ -84,7 +84,7 @@ export const openModal = ({ commit }, payload = {}) => { ...@@ -84,7 +84,7 @@ export const openModal = ({ commit }, payload = {}) => {
export const createIssue = ({ dispatch }, { vulnerability, flashError }) => { export const createIssue = ({ dispatch }, { vulnerability, flashError }) => {
dispatch('requestCreateIssue'); dispatch('requestCreateIssue');
axios axios
.post(vulnerability.vulnerability_feedback_url, { .post(vulnerability.vulnerability_feedback_issue_path, {
vulnerability_feedback: { vulnerability_feedback: {
feedback_type: 'issue', feedback_type: 'issue',
category: vulnerability.report_type, category: vulnerability.report_type,
...@@ -127,7 +127,7 @@ export const dismissVulnerability = ({ dispatch }, { vulnerability, flashError } ...@@ -127,7 +127,7 @@ export const dismissVulnerability = ({ dispatch }, { vulnerability, flashError }
dispatch('requestDismissVulnerability'); dispatch('requestDismissVulnerability');
axios axios
.post(vulnerability.vulnerability_feedback_url, { .post(vulnerability.vulnerability_feedback_dismissal_path, {
vulnerability_feedback: { vulnerability_feedback: {
feedback_type: 'dismissal', feedback_type: 'dismissal',
category: vulnerability.report_type, category: vulnerability.report_type,
...@@ -167,9 +167,9 @@ export const receiveDismissVulnerabilityError = ({ commit }, { flashError }) => ...@@ -167,9 +167,9 @@ export const receiveDismissVulnerabilityError = ({ commit }, { flashError }) =>
}; };
export const revertDismissal = ({ dispatch }, { vulnerability, flashError }) => { export const revertDismissal = ({ dispatch }, { vulnerability, flashError }) => {
const { vulnerability_feedback_url, dismissal_feedback } = vulnerability; const { vulnerability_feedback_dismissal_path, dismissal_feedback } = vulnerability;
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
const url = `${vulnerability_feedback_url}/${dismissal_feedback.id}`; const url = `${vulnerability_feedback_dismissal_path}/${dismissal_feedback.id}`;
dispatch('requestRevertDismissal'); dispatch('requestRevertDismissal');
......
...@@ -68,7 +68,7 @@ module Vulnerabilities ...@@ -68,7 +68,7 @@ module Vulnerabilities
scope :by_severities, -> (values) { where(severity: values) } scope :by_severities, -> (values) { where(severity: values) }
scope :all_preloaded, -> do scope :all_preloaded, -> do
preload(:scanner, :identifiers, :project) preload(:scanner, :identifiers, project: [:namespace, :project_feature])
end end
def self.for_pipelines(pipelines) def self.for_pipelines(pipelines)
......
...@@ -7,7 +7,8 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity ...@@ -7,7 +7,8 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity
expose :scanner, using: Vulnerabilities::ScannerEntity expose :scanner, using: Vulnerabilities::ScannerEntity
expose :identifiers, using: Vulnerabilities::IdentifierEntity expose :identifiers, using: Vulnerabilities::IdentifierEntity
expose :project_fingerprint expose :project_fingerprint
expose :vulnerability_feedback_url, if: ->(*) { can_admin_vulnerability_feedback? } expose :vulnerability_feedback_path, as: :vulnerability_feedback_issue_path, if: ->(_, _) { can_admin_vulnerability_feedback? && can_create_issue? }
expose :vulnerability_feedback_path, as: :vulnerability_feedback_dismissal_path, if: ->(_, _) { can_admin_vulnerability_feedback? }
expose :project, using: ::ProjectEntity expose :project, using: ::ProjectEntity
expose :dismissal_feedback, using: Vulnerabilities::FeedbackEntity expose :dismissal_feedback, using: Vulnerabilities::FeedbackEntity
expose :issue_feedback, using: Vulnerabilities::FeedbackEntity expose :issue_feedback, using: Vulnerabilities::FeedbackEntity
...@@ -23,11 +24,15 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity ...@@ -23,11 +24,15 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity
private private
def vulnerability_feedback_url def vulnerability_feedback_path
project_vulnerability_feedback_index_url(occurrence.project) project_vulnerability_feedback_index_path(occurrence.project)
end end
def can_admin_vulnerability_feedback? def can_admin_vulnerability_feedback?
can?(request.current_user, :admin_vulnerability_feedback, occurrence.project) can?(request.current_user, :admin_vulnerability_feedback, occurrence.project)
end end
def can_create_issue?
can?(request.current_user, :create_issue, occurrence.project)
end
end end
...@@ -88,19 +88,19 @@ describe Groups::Security::VulnerabilitiesController do ...@@ -88,19 +88,19 @@ describe Groups::Security::VulnerabilitiesController do
end end
context 'with vulnerability feedback' do context 'with vulnerability feedback' do
it "avoids N+1 queries" do it "avoids N+1 queries", :with_request_store do
create_vulnerabilities(2, project_dev, with_feedback: true) create_vulnerabilities(2, project_dev, with_feedback: true)
control_count = ActiveRecord::QueryRecorder.new { get_summary } control_count = ActiveRecord::QueryRecorder.new { get_index }
create_vulnerabilities(2, project_guest, with_feedback: true) create_vulnerabilities(2, project_guest, with_feedback: true)
expect { get_summary }.not_to exceed_all_query_limit(control_count) expect { get_index }.not_to exceed_all_query_limit(control_count)
end end
private private
def get_summary def get_index
get :index, params: { group_id: group }, format: :json get :index, params: { group_id: group }, format: :json
end end
end end
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
"properties" : { "properties" : {
"name" : { "type": "string" }, "name" : { "type": "string" },
"project_fingerprint": { "type": "string" }, "project_fingerprint": { "type": "string" },
"vulnerability_feedback_url": { "type": "string" }, "vulnerability_feedback_issue_path": { "type": "string" },
"vulnerability_feedback_dismissal_path": { "type": "string" },
"confidence" : { "confidence" : {
"type": "string", "type": "string",
"enum": ["undefined", "ignore", "unknown", "experimental", "low", "medium", "high", "critical"] "enum": ["undefined", "ignore", "unknown", "experimental", "low", "medium", "high", "critical"]
......
...@@ -363,7 +363,7 @@ describe('issue creation', () => { ...@@ -363,7 +363,7 @@ describe('issue creation', () => {
describe('on success', () => { describe('on success', () => {
beforeEach(() => { beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(200, { data }); mock.onPost(vulnerability.vulnerability_feedback_issue_path).replyOnce(200, { data });
}); });
it('should dispatch the request and success actions', done => { it('should dispatch the request and success actions', done => {
...@@ -386,7 +386,7 @@ describe('issue creation', () => { ...@@ -386,7 +386,7 @@ describe('issue creation', () => {
describe('on error', () => { describe('on error', () => {
beforeEach(() => { beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(404, {}); mock.onPost(vulnerability.vulnerability_feedback_issue_path).replyOnce(404, {});
}); });
it('should dispatch the request and error actions', done => { it('should dispatch the request and error actions', done => {
...@@ -475,7 +475,7 @@ describe('vulnerability dismissal', () => { ...@@ -475,7 +475,7 @@ describe('vulnerability dismissal', () => {
describe('on success', () => { describe('on success', () => {
beforeEach(() => { beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(200, data); mock.onPost(vulnerability.vulnerability_feedback_dismissal_path).replyOnce(200, data);
}); });
it('should dispatch the request and success actions', done => { it('should dispatch the request and success actions', done => {
...@@ -498,7 +498,7 @@ describe('vulnerability dismissal', () => { ...@@ -498,7 +498,7 @@ describe('vulnerability dismissal', () => {
describe('on error', () => { describe('on error', () => {
beforeEach(() => { beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(404, {}); mock.onPost(vulnerability.vulnerability_feedback_dismissal_path).replyOnce(404, {});
}); });
it('should dispatch the request and error actions', done => { it('should dispatch the request and error actions', done => {
...@@ -574,7 +574,7 @@ describe('vulnerability dismissal', () => { ...@@ -574,7 +574,7 @@ describe('vulnerability dismissal', () => {
describe('revert vulnerability dismissal', () => { describe('revert vulnerability dismissal', () => {
describe('revertDismissal', () => { describe('revertDismissal', () => {
const vulnerability = mockDataVulnerabilities[2]; const vulnerability = mockDataVulnerabilities[2];
const url = `${vulnerability.vulnerability_feedback_url}/${ const url = `${vulnerability.vulnerability_feedback_dismissal_path}/${
vulnerability.dismissal_feedback.id vulnerability.dismissal_feedback.id
}`; }`;
let mock; let mock;
......
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
}, },
"dismissal_feedback": null, "dismissal_feedback": null,
"issue_feedback": null, "issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback", "vulnerability_feedback_issue_path": "https://example.com/vulnerability_feedback",
"vulnerability_feedback_dismissal_path": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1", "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.", "solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": { "location": {
...@@ -94,7 +95,8 @@ ...@@ -94,7 +95,8 @@
}, },
"dismissal_feedback": null, "dismissal_feedback": null,
"issue_feedback": null, "issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback", "vulnerability_feedback_issue_path": "https://example.com/vulnerability_feedback",
"vulnerability_feedback_dismissal_path": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1", "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.", "solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": { "location": {
...@@ -166,7 +168,8 @@ ...@@ -166,7 +168,8 @@
"project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4" "project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4"
}, },
"issue_feedback": null, "issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback", "vulnerability_feedback_issue_path": "https://example.com/vulnerability_feedback",
"vulnerability_feedback_dismissal_path": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1", "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.", "solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": { "location": {
...@@ -239,7 +242,8 @@ ...@@ -239,7 +242,8 @@
"branch": "master", "branch": "master",
"project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4" "project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4"
}, },
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback", "vulnerability_feedback_issue_path": "https://example.com/vulnerability_feedback",
"vulnerability_feedback_dismissal_path": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1", "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.", "solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": { "location": {
...@@ -334,7 +338,8 @@ ...@@ -334,7 +338,8 @@
"branch": "master", "branch": "master",
"project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4" "project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4"
}, },
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback", "vulnerability_feedback_issue_path": "https://example.com/vulnerability_feedback",
"vulnerability_feedback_dismissal_path": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1", "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.", "solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": { "location": {
...@@ -384,7 +389,8 @@ ...@@ -384,7 +389,8 @@
}, },
"dismissal_feedback": null, "dismissal_feedback": null,
"issue_feedback": null, "issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback", "vulnerability_feedback_issue_path": "https://example.com/vulnerability_feedback",
"vulnerability_feedback_dismissal_path": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1", "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.", "solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": { "location": {
......
...@@ -61,8 +61,9 @@ describe Vulnerabilities::OccurrenceEntity do ...@@ -61,8 +61,9 @@ describe Vulnerabilities::OccurrenceEntity do
project.add_guest(user) project.add_guest(user)
end end
it 'does not contain vulnerability feedback URL' do it 'does not contain vulnerability feedback paths' do
expect(subject).not_to include(:vulnerability_feedback_url) expect(subject).not_to include(:vulnerability_feedback_issue_path)
expect(subject).not_to include(:vulnerability_feedback_dismissal_path)
end end
end end
...@@ -71,8 +72,24 @@ describe Vulnerabilities::OccurrenceEntity do ...@@ -71,8 +72,24 @@ describe Vulnerabilities::OccurrenceEntity do
project.add_developer(user) project.add_developer(user)
end end
it 'contains vulnerability feedback URL' do it 'contains vulnerability feedback dismissal path' do
expect(subject).to include(:vulnerability_feedback_url) expect(subject).to include(:vulnerability_feedback_dismissal_path)
end
it 'contains vulnerability feedback issue path' do
expect(subject).to include(:vulnerability_feedback_issue_path)
end
context 'when disallowed to create issue' do
let(:project) { create(:project, issues_access_level: ProjectFeature::DISABLED) }
it 'does not contain vulnerability feedback issue path' do
expect(subject).not_to include(:vulnerability_feedback_issue_path)
end
it 'contains vulnerability feedback dismissal path' do
expect(subject).to include(:vulnerability_feedback_dismissal_path)
end
end end
end end
end end
......
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