Commit 35baa114 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch...

Merge branch '215110-dast-vulnerabilities-show-more-information-about-the-request-frontend' into 'master'

Add request information to vulnerability-detail modal

See merge request gitlab-org/gitlab!31422
parents 9086b62b 4e4e3994
......@@ -6,11 +6,26 @@ export default {
type: String,
required: true,
},
maxHeight: {
type: String,
required: false,
default: 'initial',
},
},
computed: {
styleObject() {
const { maxHeight } = this;
const isScrollable = maxHeight !== 'initial';
const scrollableStyles = {
maxHeight,
overflowY: 'auto',
};
return isScrollable ? scrollableStyles : null;
},
},
};
</script>
<template>
<pre class="code-block rounded">
<code class="d-block">{{ code }}</code>
</pre>
<pre class="code-block rounded" :style="styleObject"><code class="d-block">{{ code }}</code></pre>
</template>
......@@ -38,7 +38,7 @@ Running the pipeline on any other commit has no effect on the merge request.
By clicking on one of the detected linked vulnerabilities, you can
see the details and the URL(s) affected.
![DAST Widget Clicked](img/dast_single_v12_9.png)
![DAST Widget Clicked](img/dast_single_v13_0.png)
[Dynamic Application Security Testing (DAST)](https://en.wikipedia.org/wiki/Dynamic_Application_Security_Testing)
uses the popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
......
......@@ -12,8 +12,8 @@ export default {
<template functional>
<div class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-3 text-sm-right font-weight-bold pl-0">{{ props.label }}:</label>
<div class="col-sm-9 pl-0 text-secondary">
<label class="col-sm-4 text-sm-right font-weight-bold pl-0">{{ props.label }}:</label>
<div class="col-sm-8 pl-0 text-secondary">
<slot></slot>
</div>
</div>
......
<script>
import { GlFriendlyWrap } from '@gitlab/ui';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
......@@ -12,6 +13,7 @@ import { REPORT_TYPES } from 'ee/security_dashboard/store/constants';
export default {
name: 'VulnerabilityDetails',
components: {
CodeBlock,
ExpandButton,
GlFriendlyWrap,
Icon,
......@@ -27,7 +29,7 @@ export default {
},
computed: {
url() {
return getFileLocation(this.vulnerability.location);
return this.vulnerability.request?.url || getFileLocation(this.vulnerability.location);
},
file() {
const file = this.vulnerability?.location?.file;
......@@ -69,6 +71,15 @@ export default {
instances() {
return this.asNonEmptyListOrNull(this.vulnerability.instances);
},
requestHeaders() {
return this.headersToFormattedString(this.vulnerability.request?.headers);
},
responseHeaders() {
return this.headersToFormattedString(this.vulnerability.response?.headers);
},
responseStatusCode() {
return this.vulnerability.response?.status_code;
},
scannerType() {
return REPORT_TYPES[this.vulnerability.report_type];
},
......@@ -99,6 +110,9 @@ export default {
asNonEmptyListOrNull(list) {
return list?.length > 0 ? list : null;
},
headersToFormattedString(headers = []) {
return headers.map(({ name, value }) => `${name}: ${value}`).join('\n');
},
},
};
</script>
......@@ -118,12 +132,28 @@ export default {
</safe-link>
</vulnerability-detail>
<vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
<gl-friendly-wrap :text="methodName" />
</vulnerability-detail>
<vulnerability-detail v-if="url" :label="__('URL')">
<safe-link ref="urlLink" :href="url" target="_blank">
<gl-friendly-wrap :text="url" />
</safe-link>
</vulnerability-detail>
<vulnerability-detail v-if="requestHeaders" :label="__('Request Headers')">
<code-block ref="requestHeaders" :code="requestHeaders" max-height="225px" />
</vulnerability-detail>
<vulnerability-detail v-if="responseStatusCode" :label="__('Response Status')">
<gl-friendly-wrap ref="responseStatusCode" :text="responseStatusCode" />
</vulnerability-detail>
<vulnerability-detail v-if="responseHeaders" :label="__('Response Headers')">
<code-block ref="responseHeaders" :code="responseHeaders" max-height="225px" />
</vulnerability-detail>
<vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
<safe-link
v-if="vulnerability.blob_path"
......@@ -177,10 +207,6 @@ export default {
<gl-friendly-wrap :text="className" />
</vulnerability-detail>
<vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
<gl-friendly-wrap :text="methodName" />
</vulnerability-detail>
<vulnerability-detail v-if="image" :label="s__('Vulnerability|Image')">
<gl-friendly-wrap :text="image" />
</vulnerability-detail>
......
---
title: Add request information to vulnerability-detail modal
merge_request: 31422
author:
type: changed
......@@ -5,13 +5,13 @@ exports[`VulnerabilityDetail component renders the label prop and default slot 1
class="d-sm-flex my-sm-2 my-4"
>
<label
class="col-sm-3 text-sm-right font-weight-bold pl-0"
class="col-sm-4 text-sm-right font-weight-bold pl-0"
>
foo:
</label>
<div
class="col-sm-9 pl-0 text-secondary"
class="col-sm-8 pl-0 text-secondary"
>
<p>
bar
......
......@@ -29,6 +29,49 @@ exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
<!---->
<vulnerability-detail-stub
label="URL"
>
<safe-link-stub
href="http://foo.bar/path"
target="_blank"
>
<gl-friendly-wrap-stub
symbols="/"
text="http://foo.bar/path"
/>
</safe-link-stub>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Request Headers"
>
<code-block-stub
code="key1: value1
key2: value2"
maxheight="225px"
/>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Response Status"
>
<gl-friendly-wrap-stub
symbols="/"
text="200"
/>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="Response Headers"
>
<code-block-stub
code="key1: value1
key2: value2"
maxheight="225px"
/>
</vulnerability-detail-stub>
<vulnerability-detail-stub
label="File"
>
......@@ -124,8 +167,6 @@ exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
<!---->
<!---->
<vulnerability-detail-stub
label="Links"
>
......
import { mount, shallowMount } from '@vue/test-utils';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
......@@ -29,6 +30,9 @@ describe('VulnerabilityDetails component', () => {
};
const findLink = name => wrapper.find({ ref: `${name}Link` });
const findRequestHeaders = () => wrapper.find({ ref: 'requestHeaders' });
const findResponseHeaders = () => wrapper.find({ ref: 'responseHeaders' });
const findResponseStatusCode = () => wrapper.find({ ref: 'responseStatusCode' });
afterEach(() => {
wrapper.destroy();
......@@ -151,6 +155,90 @@ describe('VulnerabilityDetails component', () => {
});
});
describe('with request information', () => {
beforeEach(() => {
const vulnerability = makeVulnerability({
request: {
url: 'http://foo.bar/path',
headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
},
});
componentFactory(vulnerability);
});
it('renders the request-url', () => {
expect(findLink('url').props('href')).toBe('http://foo.bar/path');
});
it('renders a code-block containing the http headers', () => {
expect(findRequestHeaders().is(CodeBlock)).toBe(true);
expect(findRequestHeaders().text()).toBe('key1: value1\nkey2: value2');
});
it('limits the code-blocks maximum height', () => {
expect(findRequestHeaders().props('maxHeight')).not.toBeFalsy();
expect(findRequestHeaders().props('maxHeight')).toEqual(expect.any(String));
});
});
describe('without request information', () => {
beforeEach(() => {
const vulnerability = makeVulnerability({
location: {
hostname: 'http://foo.com',
path: '/bar',
},
});
componentFactory(vulnerability);
});
it('renders the location-url', () => {
expect(findLink('url').text()).toBe('http://foo.com/bar');
});
it('does not render a code block containing the request-headers', () => {
expect(findRequestHeaders().exists()).toBe(false);
});
});
describe('with response information', () => {
beforeEach(() => {
const vulnerability = makeVulnerability({
response: {
status_code: '200',
headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
},
});
componentFactory(vulnerability);
});
it('renders the response status code', () => {
expect(findResponseStatusCode().text()).toBe('200');
});
it('renders a code block containing the request-headers', () => {
const responseHeaders = findResponseHeaders();
expect(responseHeaders.is(CodeBlock)).toBe(true);
expect(responseHeaders.text()).toBe('key1: value1\nkey2: value2');
});
});
describe('without response information', () => {
beforeEach(() => {
const vulnerability = makeVulnerability();
componentFactory(vulnerability);
});
it('does not render the status code', () => {
expect(findResponseStatusCode().exists()).toBe(false);
});
it('does not render the http-headers', () => {
expect(findResponseHeaders().exists()).toBe(false);
});
});
describe('scanner details', () => {
describe('with additional information', () => {
beforeEach(() => {
......@@ -211,6 +299,14 @@ describe('VulnerabilityDetails component', () => {
uri: '/bar',
},
],
request: {
url: 'http://foo.bar/path',
headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
},
response: {
status_code: '200',
headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
},
}),
);
......
......@@ -17863,6 +17863,9 @@ msgstr ""
msgid "Request Access"
msgstr ""
msgid "Request Headers"
msgstr ""
msgid "Request parameter %{param} is missing."
msgstr ""
......@@ -18002,6 +18005,12 @@ msgstr ""
msgid "Response"
msgstr ""
msgid "Response Headers"
msgstr ""
msgid "Response Status"
msgstr ""
msgid "Response didn't include `service_desk_address`"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Code Block matches snapshot 1`] = `
exports[`Code Block with default props renders correctly 1`] = `
<pre
class="code-block rounded"
>
<code
class="d-block"
>
test-code
</code>
</pre>
`;
exports[`Code Block with maxHeight set to "200px" renders correctly 1`] = `
<pre
class="code-block rounded"
style="max-height: 200px; overflow-y: auto;"
>
<code
class="d-block"
>
test-code
</code>
</pre>
`;
......@@ -4,10 +4,15 @@ import CodeBlock from '~/vue_shared/components/code_block.vue';
describe('Code Block', () => {
let wrapper;
const createComponent = () => {
const defaultProps = {
code: 'test-code',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(CodeBlock, {
propsData: {
code: 'test-code',
...defaultProps,
...props,
},
});
};
......@@ -17,9 +22,23 @@ describe('Code Block', () => {
wrapper = null;
});
it('matches snapshot', () => {
createComponent();
describe('with default props', () => {
beforeEach(() => {
createComponent();
});
expect(wrapper.element).toMatchSnapshot();
it('renders correctly', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('with maxHeight set to "200px"', () => {
beforeEach(() => {
createComponent({ maxHeight: '200px' });
});
it('renders correctly', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
});
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