Commit ae4384f2 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'replace-safe-link' into 'master'

Replace SafeLink Component with GlLink

Closes #216040

See merge request gitlab-org/gitlab!31083
parents d9221796 29b09aa4
<script>
import { isSafeURL } from '~/lib/utils/url_utility';
/**
* Renders a link element (`<a>`) if the href is a absolute http(s) URL,
* a `<span>` element otherwise
*/
export default {
name: 'SafeLink',
/*
The props contain all attributes specifically defined for the <a> element:
https://www.w3.org/TR/2011/WD-html5-20110113/text-level-semantics.html#the-a-element
*/
props: {
href: {
type: String,
required: true,
},
target: {
type: String,
required: false,
default: undefined,
},
rel: {
type: String,
required: false,
default: undefined,
},
media: {
type: String,
required: false,
default: undefined,
},
hreflang: {
type: String,
required: false,
default: undefined,
},
type: {
type: String,
required: false,
default: undefined,
},
},
computed: {
hasSafeHref() {
return isSafeURL(this.href);
},
componentName() {
return this.hasSafeHref ? 'a' : 'span';
},
linkAttributes() {
if (this.hasSafeHref) {
const { href, target, rel, media, hreflang, type } = this;
return { href, target, rel, media, hreflang, type };
}
return {};
},
},
};
</script>
<template>
<component :is="componentName" v-bind="linkAttributes"> <slot></slot> </component>
</template>
<script>
import { mapActions, mapState } from 'vuex';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import { GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import LicensePackages from './license_packages.vue';
......@@ -9,7 +9,7 @@ import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/const
export default {
name: 'LicenseSetApprovalStatusModal',
components: { SafeLink, LicensePackages, GlModal: DeprecatedModal2 },
components: { GlLink, LicensePackages, GlModal: DeprecatedModal2 },
computed: {
...mapState(LICENSE_MANAGEMENT, ['currentLicenseInModal', 'canManageLicenses']),
headerTitleText() {
......@@ -61,12 +61,9 @@ export default {
{{ s__('LicenseCompliance|URL') }}:
</label>
<div class="col-sm-9 text-secondary">
<safe-link
:href="currentLicenseInModal.url"
target="_blank"
rel="noopener noreferrer nofollow"
>{{ currentLicenseInModal.url }}</safe-link
>
<gl-link :href="currentLicenseInModal.url" target="_blank" rel="nofollow">{{
currentLicenseInModal.url
}}</gl-link>
</div>
</div>
<div class="row prepend-top-10 append-bottom-10 js-license-packages">
......
<script>
import { GlFriendlyWrap } from '@gitlab/ui';
import { GlFriendlyWrap, GlLink } 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';
import SeverityBadge from './severity_badge.vue';
......@@ -17,9 +16,9 @@ export default {
ExpandButton,
GlFriendlyWrap,
Icon,
SafeLink,
SeverityBadge,
VulnerabilityDetail,
GlLink,
},
props: {
vulnerability: {
......@@ -127,9 +126,9 @@ export default {
</vulnerability-detail>
<vulnerability-detail v-if="vulnerability.project" :label="s__('Vulnerability|Project')">
<safe-link ref="projectLink" :href="vulnerability.project.full_path" target="_blank">
<gl-link ref="projectLink" :href="vulnerability.project.full_path" target="_blank">
<gl-friendly-wrap :text="vulnerability.project.full_name" />
</safe-link>
</gl-link>
</vulnerability-detail>
<vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
......@@ -137,9 +136,9 @@ export default {
</vulnerability-detail>
<vulnerability-detail v-if="url" :label="__('URL')">
<safe-link ref="urlLink" :href="url" target="_blank">
<gl-link ref="urlLink" :href="url" target="_blank">
<gl-friendly-wrap :text="url" />
</safe-link>
</gl-link>
</vulnerability-detail>
<vulnerability-detail v-if="requestHeaders" :label="__('Request Headers')">
......@@ -155,14 +154,14 @@ export default {
</vulnerability-detail>
<vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
<safe-link
<gl-link
v-if="vulnerability.blob_path"
ref="fileLink"
:href="vulnerability.blob_path"
target="_blank"
>
<gl-friendly-wrap :text="file" />
</safe-link>
</gl-link>
<gl-friendly-wrap v-else :text="file" />
</vulnerability-detail>
......@@ -172,15 +171,15 @@ export default {
<vulnerability-detail v-if="identifiers" :label="s__('Vulnerability|Identifiers')">
<span v-for="(identifier, i) in identifiers" :key="i">
<safe-link
<gl-link
v-if="identifier.url"
ref="identifiersLink"
:href="identifier.url"
target="_blank"
rel="noopener noreferrer"
rel="nofollow"
>
{{ identifier.name }}
</safe-link>
</gl-link>
<span v-else> {{ identifier.name }} </span>
<span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
</span>
......@@ -198,9 +197,10 @@ export default {
</vulnerability-detail>
<vulnerability-detail v-if="scannerProvider" :label="s__('Vulnerability|Scanner Provider')">
<safe-link ref="scannerLink" :href="scannerUrl" target="_blank" rel="noopener noreferrer">
<gl-link v-if="scannerUrl" ref="scannerLink" :href="scannerUrl" target="_blank">
<gl-friendly-wrap :text="scannerProvider" />
</safe-link>
</gl-link>
<gl-friendly-wrap v-else :text="scannerProvider" />
</vulnerability-detail>
<vulnerability-detail v-if="className" :label="s__('Vulnerability|Class')">
......@@ -217,9 +217,9 @@ export default {
<vulnerability-detail v-if="links" :label="s__('Vulnerability|Links')">
<span v-for="(link, i) in links" :key="i">
<safe-link ref="linksLink" :href="link.url" target="_blank" rel="noopener noreferrer">
<gl-link ref="linksLink" :href="link.url" target="_blank">
{{ link.value || link.url }}
</safe-link>
</gl-link>
<span v-if="hasMoreValues(i, links)">,&nbsp;</span>
</span>
</vulnerability-detail>
......@@ -236,14 +236,9 @@ export default {
{{ instance.method }}
</div>
<div class="report-block-list-issue-description-link">
<safe-link
:href="instance.uri"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
<gl-link :href="instance.uri" target="_blank" rel="nofollow" class="break-link">
{{ instance.uri }}
</safe-link>
</gl-link>
</div>
<expand-button v-if="instance.evidence">
<template #expanded>
......
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import DetailItem from './detail_item.vue';
export default {
name: 'VulnerabilityDetails',
components: { GlLink, SeverityBadge, DetailItem, SafeLink, GlSprintf },
components: { GlLink, SeverityBadge, DetailItem, GlSprintf },
props: {
vulnerability: {
type: Object,
......@@ -37,6 +36,22 @@ export default {
scannerUrl() {
return this.scanner.url || '';
},
scannerDetails() {
if (this.scannerUrl) {
return {
component: 'GlLink',
properties: {
href: this.scannerUrl,
target: '_blank',
},
};
}
return {
component: 'span',
properties: {},
};
},
},
};
</script>
......@@ -63,10 +78,9 @@ export default {
v-if="scanner.name"
:sprintf-message="__('%{labelStart}Scanner:%{labelEnd} %{scanner}')"
>
<safe-link
:href="scannerUrl"
target="_blank"
rel="noopener noreferrer"
<component
:is="scannerDetails.component"
v-bind="scannerDetails.properties"
data-testid="scannerSafeLink"
>
<gl-sprintf
......@@ -77,7 +91,7 @@ export default {
<template #scannerVersion>{{ scanner.version }}</template>
</gl-sprintf>
<template v-else>{{ scanner.name }}</template>
</safe-link>
</component>
</detail-item>
<detail-item
v-if="location.image"
......
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
import Vue from 'vue';
describe('SafeLink', () => {
const Component = Vue.extend(SafeLink);
const httpLink = `${TEST_HOST}/safe_link.html`;
// eslint-disable-next-line no-script-url
const javascriptLink = 'javascript:alert("jay")';
const linkText = 'Link Text';
const linkProps = {
hreflang: 'XR',
rel: 'alternate',
type: 'text/html',
target: '_blank',
media: 'all',
};
let vm;
describe('valid link', () => {
let props;
beforeEach(() => {
props = { href: httpLink, ...linkProps };
vm = mountComponentWithSlots(Component, { props, slots: { default: [linkText] } });
});
it('renders a link element', () => {
expect(vm.$el.tagName).toEqual('A');
});
it('renders link specific attributes', () => {
expect(vm.$el.getAttribute('href')).toEqual(httpLink);
Object.keys(linkProps).forEach(key => {
expect(vm.$el.getAttribute(key)).toEqual(linkProps[key]);
});
});
it('renders the inner text as provided', () => {
expect(vm.$el.innerText).toEqual(linkText);
});
});
describe('invalid link', () => {
let props;
beforeEach(() => {
props = { href: javascriptLink, ...linkProps };
vm = mountComponentWithSlots(Component, { props, slots: { default: [linkText] } });
});
it('renders a span element', () => {
expect(vm.$el.tagName).toEqual('SPAN');
});
it('renders without link specific attributes', () => {
expect(vm.$el.getAttribute('href')).toEqual(null);
Object.keys(linkProps).forEach(key => {
expect(vm.$el.getAttribute(key)).toEqual(null);
});
});
it('renders the inner text as provided', () => {
expect(vm.$el.innerText).toEqual(linkText);
});
});
});
......@@ -339,9 +339,8 @@ describe('SetApprovalModal', () => {
expect(licenseName).not.toBeNull();
expect(trimText(licenseName.innerText)).toBe(`URL: ${badURL}`);
expect(licenseName.querySelector('a')).toBeNull();
expect(licenseName.querySelector('span')).not.toBeNull();
expect(licenseName.querySelector('span').innerText).toBe(badURL);
expect(licenseName.querySelector('a').getAttribute('href')).toBe('about:blank');
expect(licenseName.querySelector('a').innerText).toBe(badURL);
})
.then(done)
.catch(done.fail);
......
......@@ -16,7 +16,7 @@ exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
<vulnerability-detail-stub
label="Project"
>
<safe-link-stub
<gl-link-stub
href="/gitlab-org/gitlab-ui"
target="_blank"
>
......@@ -24,7 +24,7 @@ exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
symbols="/"
text="GitLab.org / gitlab-ui"
/>
</safe-link-stub>
</gl-link-stub>
</vulnerability-detail-stub>
<!---->
......@@ -32,7 +32,7 @@ exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
<vulnerability-detail-stub
label="URL"
>
<safe-link-stub
<gl-link-stub
href="http://foo.bar/path"
target="_blank"
>
......@@ -40,7 +40,7 @@ exports[`VulnerabilityDetails component pin test renders correctly 1`] = `
symbols="/"
text="http://foo.bar/path"
/>
</safe-link-stub>
</gl-link-stub>
</vulnerability-detail-stub>
<vulnerability-detail-stub
......@@ -75,7 +75,7 @@ key2: value2"
<vulnerability-detail-stub
label="File"
>
<safe-link-stub
<gl-link-stub
href="/gitlab-org/gitlab-ui/blob/ad137f0a8ac59af961afe47d04e5cc062c6864a9/yarn.lock"
target="_blank"
>
......@@ -83,7 +83,7 @@ key2: value2"
symbols="/"
text="yarn.lock"
/>
</safe-link-stub>
</gl-link-stub>
</vulnerability-detail-stub>
<vulnerability-detail-stub
......@@ -99,30 +99,30 @@ key2: value2"
label="Identifiers"
>
<span>
<safe-link-stub
<gl-link-stub
href="https://deps.sec.gitlab.com/packages/npm/serialize-javascript/versions/1.7.0/advisories"
rel="noopener noreferrer"
rel="nofollow"
target="_blank"
>
Gemnasium-58caa017-9a9a-46d6-bab2-ec930f46833c
</safe-link-stub>
</gl-link-stub>
<span>
</span>
</span>
<span>
<safe-link-stub
<gl-link-stub
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16769"
rel="noopener noreferrer"
rel="nofollow"
target="_blank"
>
CVE-2019-16769
</safe-link-stub>
</gl-link-stub>
<!---->
</span>
......@@ -149,16 +149,15 @@ key2: value2"
<vulnerability-detail-stub
label="Scanner Provider"
>
<safe-link-stub
<gl-link-stub
href="https://gitlab.com/gitlab-org/security-products/gemnasium"
rel="noopener noreferrer"
target="_blank"
>
<gl-friendly-wrap-stub
symbols="/"
text="Gemnasium (version 1.1.1)"
/>
</safe-link-stub>
</gl-link-stub>
</vulnerability-detail-stub>
<!---->
......@@ -171,15 +170,14 @@ key2: value2"
label="Links"
>
<span>
<safe-link-stub
<gl-link-stub
href="https://nvd.nist.gov/vuln/detail/CVE-2019-16769"
rel="noopener noreferrer"
target="_blank"
>
https://nvd.nist.gov/vuln/detail/CVE-2019-16769
</safe-link-stub>
</gl-link-stub>
<!---->
</span>
......@@ -220,16 +218,16 @@ key2: value2"
<div
class="report-block-list-issue-description-link"
>
<safe-link-stub
<gl-link-stub
class="break-link"
href="/bar"
rel="noopener noreferrer nofollow"
rel="nofollow"
target="_blank"
>
/bar
</safe-link-stub>
</gl-link-stub>
</div>
<expand-button-stub />
......
......@@ -2,10 +2,10 @@ 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';
import { TEST_HOST } from 'helpers/test_constants';
import { cloneDeep } from 'lodash';
import { mockFindings } from '../mock_data';
import { GlLink } from '@gitlab/ui';
function makeVulnerability(changes = {}) {
return Object.assign(cloneDeep(mockFindings[0]), changes);
......@@ -21,11 +21,11 @@ describe('VulnerabilityDetails component', () => {
};
const expectSafeLink = ({ link, href, text, isExternal = true }) => {
expect(link.is(SafeLink)).toBe(true);
expect(link.props('href')).toBe(href);
expect(link.is(GlLink)).toBe(true);
expect(link.attributes('href')).toBe(href);
expect(link.text()).toBe(text);
if (isExternal) {
expect(link.props('rel')).toContain('noopener noreferrer');
expect(link.attributes('rel')).toContain('noopener noreferrer');
}
};
......@@ -105,17 +105,17 @@ describe('VulnerabilityDetails component', () => {
});
it('for the link field', () => {
expectSafeLink({ link: findLink('links'), href: badUrl, text: badUrl });
expectSafeLink({ link: findLink('links'), href: 'about:blank', text: badUrl });
});
it('for the identifiers field', () => {
expectSafeLink({ link: findLink('identifiers'), href: badUrl, text: 'BAD_URL' });
expectSafeLink({ link: findLink('identifiers'), href: 'about:blank', text: 'BAD_URL' });
});
it('for the file field', () => {
expectSafeLink({
link: findLink('file'),
href: badUrl,
href: 'about:blank',
text: 'badFile.lock',
isExternal: false,
});
......@@ -124,7 +124,7 @@ describe('VulnerabilityDetails component', () => {
it('for the instances field', () => {
expectSafeLink({
link: wrapper.find('.report-block-list-issue-description-link .break-link'),
href: badUrl,
href: 'about:blank',
text: badUrl,
});
});
......@@ -167,7 +167,7 @@ describe('VulnerabilityDetails component', () => {
});
it('renders the request-url', () => {
expect(findLink('url').props('href')).toBe('http://foo.bar/path');
expect(findLink('url').attributes('href')).toBe('http://foo.bar/path');
});
it('renders a code-block containing the http headers', () => {
......@@ -266,16 +266,8 @@ describe('VulnerabilityDetails component', () => {
componentFactory(vulnerability);
});
it('should not display version', () => {
expectSafeLink({
link: findLink('scanner'),
href: '',
text: 'Clair',
});
});
it('should not render link', () => {
expect(findLink('scanner').contains('a')).toBe(false);
it('should not render the link', () => {
expect(findLink('scanner').exists()).toBe(false);
});
});
});
......
......@@ -152,25 +152,25 @@ describe('Vulnerability Details', () => {
it('shows the scanner name only but no link', () => {
createWrapper({ scanner: { name: 'some scanner' } });
expect(scannerText()).toBe('Scanner: some scanner');
expect(link().vm.$el instanceof HTMLSpanElement).toBe(true);
expect(link().element instanceof HTMLSpanElement).toBe(true);
});
it('shows the scanner name and version but no link', () => {
createWrapper({ scanner: { name: 'some scanner', version: '1.2.3' } });
expect(scannerText()).toBe('Scanner: some scanner (version 1.2.3)');
expect(link().vm.$el instanceof HTMLSpanElement).toBe(true);
expect(link().element instanceof HTMLSpanElement).toBe(true);
});
it('shows the scanner name only with a link', () => {
createWrapper({ scanner: { name: 'some scanner', url: '//link' } });
expect(scannerText()).toBe('Scanner: some scanner');
expect(link().props('href')).toBe('//link');
expect(link().attributes('href')).toBe('//link');
});
it('shows the scanner name and version with a link', () => {
createWrapper({ scanner: { name: 'some scanner', version: '1.2.3', url: '//link' } });
expect(scannerText()).toBe('Scanner: some scanner (version 1.2.3)');
expect(link().props('href')).toBe('//link');
expect(link().attributes('href')).toBe('//link');
});
});
});
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