Commit 6781c46d authored by Filipa Lacerda's avatar Filipa Lacerda

Does not render modalbox footer when user has no persmissions

parent a8fe17de
......@@ -2,11 +2,7 @@
import $ from 'jquery';
import Icon from '~/vue_shared/components/icon.vue';
import { inserted } from '~/feature_highlight/feature_highlight_helper';
import {
mouseenter,
debouncedMouseleave,
togglePopover,
} from '~/shared/popover';
import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
export default {
name: 'SecurityReportsHelpPopover',
......@@ -22,21 +18,22 @@ export default {
mounted() {
const $el = $(this.$el);
$el.popover({
html: true,
trigger: 'focus',
container: 'body',
placement: 'top',
template:
'<div class="popover" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>',
...this.options,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave(300))
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
});
$el
.popover({
html: true,
trigger: 'focus',
container: 'body',
placement: 'top',
template:
'<div class="popover" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>',
...this.options,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave(300))
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
});
},
};
</script>
......
<script>
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Modal from '~/vue_shared/components/gl_modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Modal from '~/vue_shared/components/gl_modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
export default {
components: {
Modal,
LoadingButton,
ExpandButton,
Icon,
export default {
components: {
Modal,
LoadingButton,
ExpandButton,
Icon,
},
computed: {
...mapState([
'modal',
'vulnerabilityFeedbackHelpPath',
'canCreateIssuePermission',
'canCreateFeedbackPermission',
]),
revertTitle() {
return this.modal.vulnerability.isDismissed
? s__('ciReport|Revert dismissal')
: s__('ciReport|Dismiss vulnerability');
},
computed: {
...mapState([
'modal',
'vulnerabilityFeedbackHelpPath',
'canCreateIssuePermission',
'canCreateFeedbackPermission',
]),
revertTitle() {
return this.modal.vulnerability.isDismissed
? s__('ciReport|Revert dismissal')
: s__('ciReport|Dismiss vulnerability');
},
hasDismissedBy() {
return (
this.modal.vulnerability.dismissalFeedback &&
this.modal.vulnerability.dismissalFeedback.pipeline &&
this.modal.vulnerability.dismissalFeedback.author
);
},
hasDismissedBy() {
return (
this.modal.vulnerability.dismissalFeedback &&
this.modal.vulnerability.dismissalFeedback.pipeline &&
this.modal.vulnerability.dismissalFeedback.author
);
},
methods: {
...mapActions(['dismissIssue', 'revertDismissIssue', 'createNewIssue']),
handleDismissClick() {
if (this.modal.vulnerability.isDismissed) {
this.revertDismissIssue();
} else {
this.dismissIssue();
}
},
isLastValue(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(field, key) {
return key === 'instances' && this.hasValue(field);
},
hasIdentifiers(field, key) {
return key === 'identifiers' && this.hasValue(field);
},
hasLinks(field, key) {
return key === 'links' && this.hasValue(field);
},
shouldHideModalFooter() {
return (
this.modal.isResolved ||
(!this.canCreateFeedbackPermission && !this.canCreateIssuePermission)
);
},
};
},
methods: {
...mapActions(['dismissIssue', 'revertDismissIssue', 'createNewIssue']),
handleDismissClick() {
if (this.modal.vulnerability.isDismissed) {
this.revertDismissIssue();
} else {
this.dismissIssue();
}
},
isLastValue(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(field, key) {
return key === 'instances' && this.hasValue(field);
},
hasIdentifiers(field, key) {
return key === 'identifiers' && this.hasValue(field);
},
hasLinks(field, key) {
return key === 'links' && this.hasValue(field);
},
},
};
</script>
<template>
<modal
id="modal-mrwidget-security-issue"
:header-title-text="modal.title"
:class="{'modal-hide-footer': modal.isResolved}"
:class="{ 'modal-hide-footer': shouldHideModalFooter }"
class="modal-security-report-dast"
>
<slot>
......@@ -203,7 +209,7 @@
</div>
</slot>
<div slot="footer">
<template v-if="!modal.isResolved">
<template v-if="!modal.isResolved && (canCreateFeedbackPermission || canCreateIssuePermission)">
<button
type="button"
class="btn btn-default"
......@@ -229,12 +235,13 @@
>
{{ __('View issue' ) }}
</a>
<loading-button
v-else-if="!modal.vulnerability.hasIssue && canCreateIssuePermission"
:loading="modal.isCreatingNewIssue"
:disabled="modal.isCreatingNewIssue"
:label="__('Create issue')"
container-class="btn btn-success btn-inverted"
container-class="js-create-issue-btn btn btn-success btn-inverted"
@click="createNewIssue"
/>
</template>
......
......@@ -20,7 +20,7 @@ describe('Security Reports modal', () => {
store.dispatch('setPipelineId', 123);
});
describe('wit permissions', () => {
describe('with permissions', () => {
beforeEach(() => {
store.dispatch('setCanCreateIssuePermission', true);
store.dispatch('setCanCreateFeedbackPermission', true);
......@@ -56,7 +56,7 @@ describe('Security Reports modal', () => {
},
},
},
status: 'failed',
status: 'failed',
});
vm = mountComponentWithStore(Component, {
......@@ -126,27 +126,30 @@ describe('Security Reports modal', () => {
describe('with instances', () => {
beforeEach(() => {
store.dispatch('setModalData', {
title: 'Absence of Anti-CSRF Tokens',
riskcode: '1',
riskdesc: 'Low (Medium)',
desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>',
pluginid: '123',
instances: [
{
uri: 'http://192.168.32.236:3001/explore?sort=latest_activity_desc',
method: 'GET',
evidence:
"<form class='navbar-form' action='/search' accept-charset='UTF-8' method='get'>",
},
{
uri: 'http://192.168.32.236:3001/help/user/group/subgroups/index.md',
method: 'GET',
evidence:
"<form class='navbar-form' action='/search' accept-charset='UTF-8' method='get'>",
},
],
description: ' No Anti-CSRF tokens were found in a HTML submission form. ',
solution: '',
issue: {
title: 'Absence of Anti-CSRF Tokens',
riskcode: '1',
riskdesc: 'Low (Medium)',
desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>',
pluginid: '123',
instances: [
{
uri: 'http://192.168.32.236:3001/explore?sort=latest_activity_desc',
method: 'GET',
evidence:
"<form class='navbar-form' action='/search' accept-charset='UTF-8' method='get'>",
},
{
uri: 'http://192.168.32.236:3001/help/user/group/subgroups/index.md',
method: 'GET',
evidence:
"<form class='navbar-form' action='/search' accept-charset='UTF-8' method='get'>",
},
],
description: ' No Anti-CSRF tokens were found in a HTML submission form. ',
solution: '',
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
......@@ -169,19 +172,22 @@ describe('Security Reports modal', () => {
describe('data & create issue button', () => {
beforeEach(() => {
store.dispatch('setModalData', {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
cve: 'CVE-2014-9999',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
location: {
file: 'Gemfile.lock',
issue: {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
cve: 'CVE-2014-9999',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
status: 'failed',
});
vm = mountComponentWithStore(Component, {
......@@ -248,6 +254,114 @@ describe('Security Reports modal', () => {
expect(vm.$el.querySelector('.js-dismiss-btn')).toBe(null);
expect(vm.$el.querySelector('.js-create-issue-btn')).toBe(null);
});
it('does not display the footer', () => {
expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(true);
});
});
describe('with permission to create issue', () => {
beforeEach(() => {
store.dispatch('setCanCreateIssuePermission', true);
store.dispatch('setModalData', {
issue: {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
dismissalFeedback: {
id: 1,
category: 'sast',
feedback_type: 'dismissal',
issue_id: null,
author: {
name: 'John Smith',
username: 'jsmith',
web_url: 'https;//gitlab.com/user1',
},
pipeline: {
id: 123,
path: '/jsmith/awesome-project/pipelines/123',
},
},
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
});
it('does not render dismiss button', () => {
expect(vm.$el.querySelector('.js-dismiss-btn')).toBe(null);
});
it('renders create issue button', () => {
expect(vm.$el.querySelector('.js-create-issue-btn')).not.toBe(null);
});
it('renders the footer', () => {
expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(false);
});
});
describe('with permission to dismiss issue', () => {
beforeEach(() => {
store.dispatch('setCanCreateFeedbackPermission', true);
store.dispatch('setModalData', {
issue: {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
dismissalFeedback: {
id: 1,
category: 'sast',
feedback_type: 'dismissal',
issue_id: null,
author: {
name: 'John Smith',
username: 'jsmith',
web_url: 'https;//gitlab.com/user1',
},
pipeline: {
id: 123,
path: '/jsmith/awesome-project/pipelines/123',
},
},
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
});
it('does not render create issue button', () => {
expect(vm.$el.querySelector('.js-create-issue-btn')).toBe(null);
});
it('renders create issue button and footer', () => {
expect(vm.$el.querySelector('.js-dismiss-btn')).not.toBe(null);
});
it('renders the footer', () => {
expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(false);
});
});
describe('with a resolved issue', () => {
......
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