Commit 29b09aa4 authored by Dheeraj Joshi's avatar Dheeraj Joshi Committed by Kushal Pandya

Replace SafeLink Component with GlLink

This change would deprecate the SafeLink component and
use the default feature of GlLink which now does the
same job as SafeLink
parent f1d137ec
<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