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