Skip to content

Commit

Permalink
Add rel='noopener noreferrer' to all links with target='_blank' for s…
Browse files Browse the repository at this point in the history
…ecurity.
  • Loading branch information
cjcenizal committed Jan 26, 2018
1 parent 7f182fe commit fdae4cb
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 7 deletions.
15 changes: 15 additions & 0 deletions src/components/button/__snapshots__/button.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ exports[`EuiButton props fill is rendered 1`] = `
</button>
`;

exports[`EuiButton props href secures the rel attribute when the target is _blank 1`] = `
<a
class="euiButton euiButton--primary"
href="#"
rel="noopener noreferrer"
target="_blank"
>
<span
class="euiButton__content"
>
<span />
</span>
</a>
`;

exports[`EuiButton props iconSide left is rendered 1`] = `
<button
class="euiButton euiButton--primary"
Expand Down
15 changes: 14 additions & 1 deletion src/components/button/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import checkHrefAndOnClick from '../../services/prop_types/check_href_and_onclick';
import {
checkHrefAndOnClick,
getSecureRelForTarget,
} from '../../services';

import {
ICON_TYPES,
Expand Down Expand Up @@ -43,7 +46,10 @@ export const EuiButton = ({
fill,
isDisabled,
href,
target,
rel,
onClick,
type,
...rest
}) => {

Expand Down Expand Up @@ -73,10 +79,14 @@ export const EuiButton = ({
}

if (href) {
const secureRel = getSecureRelForTarget(target, rel);

return (
<a
className={classes}
href={href}
target={target}
rel={secureRel}
{...rest}
>
<span className="euiButton__content">
Expand All @@ -91,6 +101,7 @@ export const EuiButton = ({
disabled={isDisabled}
className={classes}
onClick={onClick}
type={type}
{...rest}
>
<span className="euiButton__content">
Expand Down Expand Up @@ -124,6 +135,8 @@ EuiButton.propTypes = {
size: PropTypes.oneOf(SIZES),
isDisabled: PropTypes.bool,
href: checkHrefAndOnClick,
target: PropTypes.string,
rel: PropTypes.string,
onClick: PropTypes.func,

/**
Expand Down
11 changes: 11 additions & 0 deletions src/components/button/button.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,16 @@ describe('EuiButton', () => {
});
});
});

describe('href', () => {
it('secures the rel attribute when the target is _blank', () => {
const component = render(
<EuiButton href="#" target="_blank" />
);

expect(component)
.toMatchSnapshot();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,21 @@ exports[`EuiButtonEmpty props flush right is rendered 1`] = `
</button>
`;

exports[`EuiButtonEmpty props href secures the rel attribute when the target is _blank 1`] = `
<a
class="euiButtonEmpty euiButtonEmpty--primary"
href="#"
rel="noopener noreferrer"
target="_blank"
>
<span
class="euiButtonEmpty__content"
>
<span />
</span>
</a>
`;

exports[`EuiButtonEmpty props iconSide left is rendered 1`] = `
<button
class="euiButtonEmpty euiButtonEmpty--primary"
Expand Down
16 changes: 15 additions & 1 deletion src/components/button/button_empty/button_empty.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import checkHrefAndOnClick from '../../../services/prop_types/check_href_and_onclick';
import {
checkHrefAndOnClick,
getSecureRelForTarget,
} from '../../../services';

import {
ICON_TYPES,
Expand Down Expand Up @@ -51,7 +54,10 @@ export const EuiButtonEmpty = ({
flush,
isDisabled,
href,
target,
rel,
onClick,
type,
...rest
}) => {

Expand Down Expand Up @@ -79,10 +85,14 @@ export const EuiButtonEmpty = ({
}

if (href) {
const secureRel = getSecureRelForTarget(target, rel);

return (
<a
className={classes}
href={href}
target={target}
rel={secureRel}
{...rest}
>
<span className="euiButtonEmpty__content">
Expand All @@ -97,6 +107,7 @@ export const EuiButtonEmpty = ({
disabled={isDisabled}
className={classes}
onClick={onClick}
type={type}
{...rest}
>
<span className="euiButtonEmpty__content">
Expand All @@ -118,7 +129,10 @@ EuiButtonEmpty.propTypes = {
flush: PropTypes.oneOf(FLUSH_TYPES),
isDisabled: PropTypes.bool,
href: checkHrefAndOnClick,
target: PropTypes.string,
rel: PropTypes.string,
onClick: PropTypes.func,
type: PropTypes.string,
};

EuiButtonEmpty.defaultProps = {
Expand Down
11 changes: 11 additions & 0 deletions src/components/button/button_empty/button_empty.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,16 @@ describe('EuiButtonEmpty', () => {
});
});
});

describe('href', () => {
it('secures the rel attribute when the target is _blank', () => {
const component = render(
<EuiButtonEmpty href="#" target="_blank" />
);

expect(component)
.toMatchSnapshot();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ exports[`EuiButtonIcon props color text is rendered 1`] = `
/>
`;

exports[`EuiButtonIcon props href secures the rel attribute when the target is _blank 1`] = `
<a
aria-label="button"
class="euiButtonIcon euiButtonIcon--primary"
href="#"
rel="noopener noreferrer"
target="_blank"
/>
`;

exports[`EuiButtonIcon props iconType is rendered 1`] = `
<button
aria-label="button"
Expand Down
16 changes: 15 additions & 1 deletion src/components/button/button_icon/button_icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import checkHrefAndOnClick from '../../../services/prop_types/check_href_and_onclick';
import {
checkHrefAndOnClick,
getSecureRelForTarget,
} from '../../../services';

import {
ICON_TYPES,
Expand Down Expand Up @@ -41,6 +44,9 @@ export const EuiButtonIcon = ({
isDisabled,
href,
onClick,
type,
target,
rel,
...rest
}) => {

Expand All @@ -65,10 +71,14 @@ export const EuiButtonIcon = ({
}

if (href) {
const secureRel = getSecureRelForTarget(target, rel);

return (
<a
className={classes}
href={href}
target={target}
rel={secureRel}
{...rest}
>
{buttonIcon}
Expand All @@ -80,6 +90,7 @@ export const EuiButtonIcon = ({
disabled={isDisabled}
className={classes}
onClick={onClick}
type={type}
{...rest}
>
{buttonIcon}
Expand All @@ -96,7 +107,10 @@ EuiButtonIcon.propTypes = {
isDisabled: PropTypes.bool,
'aria-label': accessibleButtonIcon,
href: checkHrefAndOnClick,
target: PropTypes.string,
rel: PropTypes.string,
onClick: PropTypes.func,
type: PropTypes.string,
};

EuiButtonIcon.defaultProps = {
Expand Down
11 changes: 11 additions & 0 deletions src/components/button/button_icon/button_icon.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,16 @@ describe('EuiButtonIcon', () => {
});
});
});

describe('href', () => {
it('secures the rel attribute when the target is _blank', () => {
const component = render(
<EuiButtonIcon aria-label="button" href="#" target="_blank" />
);

expect(component)
.toMatchSnapshot();
});
});
});
});
1 change: 1 addition & 0 deletions src/components/link/__snapshots__/link.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ exports[`EuiLink is rendered 1`] = `
class="euiLink euiLink--primary testClass1 testClass2"
data-test-subj="test subject string"
href="#"
rel="noopener noreferrer"
target="_blank"
/>
`;
22 changes: 19 additions & 3 deletions src/components/link/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import {
checkHrefAndOnClick,
getSecureRelForTarget,
} from '../../services';

const colorsToClassNameMap = {
'primary': 'euiLink--primary',
'subdued': 'euiLink--subdued',
Expand All @@ -16,30 +21,38 @@ export const COLORS = Object.keys(colorsToClassNameMap);

export const EuiLink = ({
children,
type,
color,
className,
href,
target,
rel,
onClick,
type,
...rest
}) => {
const classes = classNames('euiLink', colorsToClassNameMap[color], className);

if (onClick) {
return (
<button
type={type}
className={classes}
onClick={onClick}
type={type}
{...rest}
>
{children}
</button>
);
}

const secureRel = getSecureRelForTarget(target, rel);

return (
<a
className={classes}
href={href}
target={target}
rel={secureRel}
{...rest}
>
{children}
Expand All @@ -50,9 +63,12 @@ export const EuiLink = ({
EuiLink.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
href: checkHrefAndOnClick,
target: PropTypes.string,
rel: PropTypes.string,
onClick: PropTypes.func,
color: PropTypes.oneOf(COLORS),
type: PropTypes.string,
color: PropTypes.oneOf(COLORS),
};

EuiLink.defaultProps = {
Expand Down
8 changes: 8 additions & 0 deletions src/services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export {
Pager,
} from './paging';

export {
checkHrefAndOnClick,
} from './prop_types';

export {
getSecureRelForTarget,
} from './security';

export {
SortableProperties,
} from './sort';
Expand Down
2 changes: 1 addition & 1 deletion src/services/prop_types/check_href_and_onclick.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default function checkHrefAndOnClick(props, propName, componentName) {
export function checkHrefAndOnClick(props, propName, componentName) {
if (props.href && props.onClick) {
throw new Error(
`${componentName} must either specify an href property (if it should be a link) ` +
Expand Down
1 change: 1 addition & 0 deletions src/services/prop_types/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { checkHrefAndOnClick } from './check_href_and_onclick';
Loading

0 comments on commit fdae4cb

Please sign in to comment.