Skip to content

Commit 86a883b

Browse files
AnnMarieWtcbegley
andauthored
Sanitize href props with xss vulnerability V2 (#1000)
* Sanitize href props with xss vulnerability V2 * Sanitize href props with xss vulnerability V2 * Add @testing-library/dom * add test * fixed test * lint * lint * fixed warning * Use pytest mark parameterise * Refactor prop destructuring --------- Co-authored-by: tcbegley <[email protected]>
1 parent d6623d7 commit 86a883b

File tree

14 files changed

+3807
-1718
lines changed

14 files changed

+3807
-1718
lines changed

package-lock.json

Lines changed: 3602 additions & 1655 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@babel/plugin-transform-runtime": "^7.15.0",
4040
"@babel/preset-env": "^7.15.0",
4141
"@babel/preset-react": "^7.14.5",
42+
"@testing-library/dom": "^9.3.4",
4243
"@testing-library/jest-dom": "^5.14.1",
4344
"@testing-library/react": "^12.1.1",
4445
"@testing-library/user-event": "^13.2.1",
@@ -56,6 +57,7 @@
5657
"webpack-dev-server": "^4.7.4"
5758
},
5859
"dependencies": {
60+
"@braintree/sanitize-url": "^7.0.0",
5961
"@plotly/dash-component-plugins": "^1.2.0",
6062
"classnames": "^2.2.6",
6163
"fast-isnumeric": "^1.1.3",

src/components/badge/Badge.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {omit} from 'ramda';
44
import RBBadge from 'react-bootstrap/Badge';
55
import Link from '../../private/Link';
66
import {bootstrapColors} from '../../private/BootstrapColors';
7+
import {sanitizeAndCheckUrl} from '../../private/util';
78

89
/**
910
* Badges can be used to add counts or labels to other components.
@@ -22,6 +23,8 @@ const Badge = props => {
2223
...otherProps
2324
} = props;
2425

26+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
27+
2528
const incrementClicks = () => {
2629
if (setProps) {
2730
setProps({
@@ -36,8 +39,8 @@ const Badge = props => {
3639

3740
return (
3841
<RBBadge
39-
as={href && Link}
40-
href={href}
42+
as={sanitizedUrl && Link}
43+
href={sanitizedUrl}
4144
bg={isBootstrapColor ? color : null}
4245
text={text_color}
4346
className={class_name || className}

src/components/breadcrumb/Breadcrumb.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,33 @@ import PropTypes from 'prop-types';
33
import RBBreadcrumb from 'react-bootstrap/Breadcrumb';
44

55
import Link from '../../private/Link';
6+
import {sanitizeAndCheckUrl} from '../../private/util';
67

78
/**
89
* Use breadcrumbs to create a navigation breadcrumb in your app.
910
*/
11+
12+
const BreadcrumbItem = ({
13+
href,
14+
setProps,
15+
external_link,
16+
label,
17+
...otherProps
18+
}) => {
19+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
20+
21+
return (
22+
<RBBreadcrumb.Item
23+
linkAs={sanitizedUrl && Link}
24+
href={sanitizedUrl}
25+
linkProps={sanitizedUrl && {external_link}}
26+
{...otherProps}
27+
>
28+
{label}
29+
</RBBreadcrumb.Item>
30+
);
31+
};
32+
1033
const Breadcrumb = ({
1134
items,
1235
tag,
@@ -16,6 +39,7 @@ const Breadcrumb = ({
1639
item_class_name,
1740
itemClassName,
1841
item_style,
42+
setProps,
1943
...otherProps
2044
}) => (
2145
<RBBreadcrumb
@@ -27,16 +51,12 @@ const Breadcrumb = ({
2751
{...otherProps}
2852
>
2953
{(items || []).map((item, idx) => (
30-
<RBBreadcrumb.Item
54+
<BreadcrumbItem
3155
key={`${item.value}${idx}`}
32-
active={item.active}
33-
linkAs={item.href && Link}
3456
className={item_class_name || itemClassName}
35-
href={item.href}
36-
linkProps={item.href && {external_link: item.external_link}}
37-
>
38-
{item.label}
39-
</RBBreadcrumb.Item>
57+
setProps={setProps}
58+
{...item}
59+
/>
4060
))}
4161
</RBBreadcrumb>
4262
);

src/components/button/Button.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import {omit} from 'ramda';
44
import RBButton from 'react-bootstrap/Button';
55
import Link from '../../private/Link';
6+
import {sanitizeAndCheckUrl} from '../../private/util';
67

78
/**
89
* A component for creating Bootstrap buttons with different style options. The
@@ -34,6 +35,8 @@ const Button = props => {
3435
...otherProps
3536
} = props;
3637

38+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
39+
3740
const incrementClicks = () => {
3841
if (!disabled && setProps) {
3942
setProps({
@@ -42,7 +45,7 @@ const Button = props => {
4245
});
4346
}
4447
};
45-
const useLink = href && !disabled;
48+
const useLink = sanitizedUrl && !disabled;
4649
otherProps[useLink ? 'preOnClick' : 'onClick'] = onClick || incrementClicks;
4750

4851
if (useLink) {
@@ -56,7 +59,7 @@ const Button = props => {
5659
as={useLink ? Link : 'button'}
5760
variant={outline ? `outline-${color}` : color}
5861
type={useLink ? undefined : type}
59-
href={disabled ? undefined : href}
62+
href={disabled ? undefined : sanitizedUrl}
6063
disabled={disabled}
6164
download={useLink ? download : undefined}
6265
name={useLink ? undefined : name}

src/components/card/CardLink.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import {omit} from 'ramda';
44
import RBCard from 'react-bootstrap/Card';
55
import Link from '../../private/Link';
6+
import {sanitizeAndCheckUrl} from '../../private/util';
67

78
/**
89
* Use card link to add consistently styled links to your cards. Links can be
@@ -15,12 +16,16 @@ const CardLink = props => {
1516
disabled,
1617
className,
1718
class_name,
19+
href,
20+
setProps,
1821
...otherProps
1922
} = props;
2023

24+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
25+
2126
const incrementClicks = () => {
22-
if (!disabled && props.setProps) {
23-
props.setProps({
27+
if (!disabled && setProps) {
28+
setProps({
2429
n_clicks: props.n_clicks + 1,
2530
n_clicks_timestamp: Date.now()
2631
});
@@ -35,8 +40,9 @@ const CardLink = props => {
3540
as={Link}
3641
preOnClick={incrementClicks}
3742
disabled={disabled}
43+
href={sanitizedUrl}
3844
className={class_name || className}
39-
{...omit(['setProps', 'n_clicks', 'n_clicks_timestamp'], otherProps)}
45+
{...omit(['n_clicks', 'n_clicks_timestamp'], otherProps)}
4046
>
4147
{children}
4248
</RBCard.Link>

src/components/dropdownmenu/DropdownMenuItem.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import RBDropdown from 'react-bootstrap/Dropdown';
55

66
import Link from '../../private/Link';
77
import {DropdownMenuContext} from '../../private/DropdownMenuContext';
8+
import {sanitizeAndCheckUrl} from '../../private/util';
89

910
/**
1011
* Use DropdownMenuItem to build up the content of a DropdownMenu.
@@ -26,6 +27,8 @@ const DropdownMenuItem = props => {
2627
...otherProps
2728
} = props;
2829

30+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
31+
2932
const context = useContext(DropdownMenuContext);
3033

3134
const handleClick = e => {
@@ -40,7 +43,7 @@ const DropdownMenuItem = props => {
4043
}
4144
};
4245

43-
const useLink = href && !disabled;
46+
const useLink = sanitizedUrl && !disabled;
4447
otherProps[useLink ? 'preOnClick' : 'onClick'] = e => handleClick(e);
4548

4649
if (header) {
@@ -52,7 +55,7 @@ const DropdownMenuItem = props => {
5255
return (
5356
<RBDropdown.Item
5457
as={useLink ? Link : 'button'}
55-
href={useLink ? href : undefined}
58+
href={useLink ? sanitizedUrl : undefined}
5659
disabled={disabled}
5760
target={useLink ? target : undefined}
5861
className={class_name || className}

src/components/listgroup/ListGroupItem.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {omit} from 'ramda';
44
import RBListGroupItem from 'react-bootstrap/ListGroupItem';
55
import Link from '../../private/Link';
66
import {bootstrapColors} from '../../private/BootstrapColors';
7+
import {sanitizeAndCheckUrl} from '../../private/util';
78

89
/**
910
* Create a single item in a `ListGroup`.
@@ -24,6 +25,8 @@ const ListGroupItem = props => {
2425
...otherProps
2526
} = props;
2627

28+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
29+
2730
const incrementClicks = () => {
2831
if (!disabled && setProps) {
2932
setProps({
@@ -33,13 +36,13 @@ const ListGroupItem = props => {
3336
}
3437
};
3538
const isBootstrapColor = bootstrapColors.has(color);
36-
const useLink = href && !disabled;
39+
const useLink = sanitizedUrl && !disabled;
3740
otherProps[useLink ? 'preOnClick' : 'onClick'] = incrementClicks;
3841

3942
return (
4043
<RBListGroupItem
4144
as={useLink ? Link : 'li'}
42-
href={href}
45+
href={sanitizedUrl}
4346
target={useLink ? target : undefined}
4447
disabled={disabled}
4548
variant={isBootstrapColor ? color : null}

src/components/nav/NavLink.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {omit} from 'ramda';
44
import classNames from 'classnames';
55
import {History} from '@plotly/dash-component-plugins';
66
import Link from '../../private/Link';
7+
import {sanitizeAndCheckUrl} from '../../private/util';
78

89
/**
910
* Add a link to a `Nav`. Can be used as a child of `NavItem` or of `Nav`
@@ -24,11 +25,13 @@ const NavLink = props => {
2425
...otherProps
2526
} = props;
2627

28+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
29+
2730
const pathnameToActive = pathname => {
2831
setLinkActive(
2932
active === true ||
30-
(active === 'exact' && pathname === href) ||
31-
(active === 'partial' && pathname.startsWith(href))
33+
(active === 'exact' && pathname === sanitizedUrl) ||
34+
(active === 'partial' && pathname.startsWith(sanitizedUrl))
3235
);
3336
};
3437

@@ -61,7 +64,7 @@ const NavLink = props => {
6164
className={classes}
6265
disabled={disabled}
6366
preOnClick={incrementClicks}
64-
href={href}
67+
href={sanitizedUrl}
6568
{...omit(['n_clicks_timestamp'], otherProps)}
6669
data-dash-is-loading={
6770
(loading_state && loading_state.is_loading) || undefined

src/components/nav/NavbarBrand.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,28 @@ import PropTypes from 'prop-types';
33
import {omit} from 'ramda';
44
import RBNavbarBrand from 'react-bootstrap/NavbarBrand';
55
import Link from '../../private/Link';
6+
import {sanitizeAndCheckUrl} from '../../private/util';
67

78
/**
89
* Call out attention to a brand name or site title within a navbar.
910
*/
1011
const NavbarBrand = props => {
11-
const {children, loading_state, className, class_name, ...otherProps} = props;
12+
const {
13+
children,
14+
loading_state,
15+
className,
16+
class_name,
17+
href,
18+
setProps,
19+
...otherProps
20+
} = props;
21+
const sanitizedUrl = sanitizeAndCheckUrl(href, setProps);
1222
return (
1323
<RBNavbarBrand
1424
className={class_name || className}
15-
{...omit(['setProps'], otherProps)}
16-
as={props.href ? Link : 'span'}
25+
{...otherProps}
26+
as={sanitizedUrl ? Link : 'span'}
27+
href={sanitizedUrl}
1728
data-dash-is-loading={
1829
(loading_state && loading_state.is_loading) || undefined
1930
}

0 commit comments

Comments
 (0)