aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2018-12-11 08:35:04 +0100
committersonartech <sonartech@sonarsource.com>2018-12-20 11:41:28 +0100
commit65e616ffa93f0e0029f792780ff8f3db0b830c2d (patch)
treea7495b271e05e44c328f19f1cc23689bfa6b3452 /server/sonar-web/src/main/js/apps
parent3a08c1730f3fe248414add8fa57a61c5670e04c9 (diff)
downloadsonarqube-65e616ffa93f0e0029f792780ff8f3db0b830c2d.tar.gz
sonarqube-65e616ffa93f0e0029f792780ff8f3db0b830c2d.zip
SONAR-11506, SSF-62 Handle XSS code in project links
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap26
-rw-r--r--server/sonar-web/src/main/js/apps/overview/styles.css17
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap49
7 files changed, 162 insertions, 18 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx
index a1e1e6bad13..4c20250f881 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx
@@ -20,18 +20,55 @@
import * as React from 'react';
import { getLinkName } from '../../projectLinks/utils';
import ProjectLinkIcon from '../../../components/icons-components/ProjectLinkIcon';
+import isValidUri from '../../../app/utils/isValidUri';
+import ClearIcon from '../../../components/icons-components/ClearIcon';
interface Props {
link: T.ProjectLink;
}
-export default function MetaLink({ link }: Props) {
- return (
- <li>
- <a className="link-with-icon" href={link.url} rel="nofollow" target="_blank">
- <ProjectLinkIcon className="little-spacer-right" type={link.type} />
- {getLinkName(link)}
- </a>
- </li>
- );
+interface State {
+ expanded: boolean;
+}
+
+export default class MetaLink extends React.PureComponent<Props, State> {
+ state = {
+ expanded: false
+ };
+
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.setState(s => ({ expanded: !s.expanded }));
+ };
+
+ render() {
+ const { link } = this.props;
+ return (
+ <li>
+ <a
+ className="link-with-icon"
+ href={link.url}
+ onClick={!isValidUri(link.url) ? this.handleClick : undefined}
+ rel="nofollow noreferrer noopener"
+ target="_blank">
+ <ProjectLinkIcon className="little-spacer-right" type={link.type} />
+ {getLinkName(link)}
+ </a>
+ {this.state.expanded && (
+ <div className="little-spacer-top copy-paste-link">
+ <input
+ className="overview-key"
+ onClick={(event: React.MouseEvent<HTMLInputElement>) => event.currentTarget.select()}
+ readOnly={true}
+ type="text"
+ value={link.url}
+ />
+ <a className="close" href="#" onClick={this.handleClick}>
+ <ClearIcon />
+ </a>
+ </div>
+ )}
+ </li>
+ );
+ }
}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx
index 0bea70841d8..23d0d3fa5eb 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx
@@ -33,6 +33,17 @@ it('should match snapshot', () => {
expect(shallow(<MetaLink link={link} />)).toMatchSnapshot();
});
+it('should render dangerous links as plaintext', () => {
+ const link = {
+ id: '1',
+ name: 'Dangerous',
+ url: 'javascript:alert("hi")',
+ type: 'dangerous'
+ };
+
+ expect(shallow(<MetaLink link={link} />)).toMatchSnapshot();
+});
+
it('should expand and collapse link', () => {
const link = {
id: '1',
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap
index 553a37c7bf1..a8f7dbb5bba 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap
@@ -5,7 +5,7 @@ exports[`should expand and collapse link 1`] = `
<a
className="link-with-icon"
href="scm:git:git@github.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
@@ -22,7 +22,7 @@ exports[`should expand and collapse link 2`] = `
<a
className="link-with-icon"
href="scm:git:git@github.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
@@ -39,7 +39,7 @@ exports[`should expand and collapse link 3`] = `
<a
className="link-with-icon"
href="scm:git:git@github.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
@@ -56,7 +56,7 @@ exports[`should match snapshot 1`] = `
<a
className="link-with-icon"
href="http://example.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
@@ -67,3 +67,21 @@ exports[`should match snapshot 1`] = `
</a>
</li>
`;
+
+exports[`should render dangerous links as plaintext 1`] = `
+<li>
+ <a
+ className="link-with-icon"
+ href="javascript:alert(\\"hi\\")"
+ onClick={[Function]}
+ rel="nofollow noreferrer noopener"
+ target="_blank"
+ >
+ <ProjectLinkIcon
+ className="little-spacer-right"
+ type="dangerous"
+ />
+ Dangerous
+ </a>
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/styles.css b/server/sonar-web/src/main/js/apps/overview/styles.css
index 06480c796cd..2ded3d250d6 100644
--- a/server/sonar-web/src/main/js/apps/overview/styles.css
+++ b/server/sonar-web/src/main/js/apps/overview/styles.css
@@ -470,6 +470,23 @@
background-color: transparent !important;
}
+.copy-paste-link .overview-key {
+ width: 90%;
+}
+
+.copy-paste-link .close {
+ color: black;
+ border-bottom: 0;
+ height: 100%;
+ display: inline-block;
+ margin-left: 5px;
+ box-sizing: border-box;
+}
+
+.copy-paste-link .close svg {
+ vertical-align: sub;
+}
+
.overview-deleted-profile,
.overview-deprecated-rules {
margin: 4px -6px 4px;
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx b/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx
index 9ca2a8f6906..c3f83b718f6 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx
@@ -23,6 +23,7 @@ import ConfirmButton from '../../components/controls/ConfirmButton';
import ProjectLinkIcon from '../../components/icons-components/ProjectLinkIcon';
import { Button } from '../../components/ui/buttons';
import { translate, translateWithParameters } from '../../helpers/l10n';
+import isValidUri from '../../app/utils/isValidUri';
interface Props {
link: T.ProjectLink;
@@ -90,9 +91,13 @@ export default class LinkRow extends React.PureComponent<Props> {
<tr data-name={link.name}>
<td className="nowrap">{this.renderName(link)}</td>
<td className="nowrap js-url">
- <a href={link.url} rel="nofollow" target="_blank">
- {link.url}
- </a>
+ {isValidUri(link.url) ? (
+ <a href={link.url} rel="nofollow noreferrer noopener" target="_blank">
+ {link.url}
+ </a>
+ ) : (
+ link.url
+ )}
</td>
<td className="thin nowrap">{this.renderDeleteButton(link)}</td>
</tr>
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx
index 19cff23dd62..3c93017c8ae 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx
@@ -42,3 +42,14 @@ it('should render custom link', () => {
)
).toMatchSnapshot();
});
+
+it('should render dangerous code as plain text', () => {
+ expect(
+ shallow(
+ <LinkRow
+ link={{ id: '12', name: 'dangerous', type: 'dangerous', url: 'javascript:alert("Hello")' }}
+ onDelete={jest.fn()}
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap
index c76c3e13aef..e05b8f94f91 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap
@@ -28,7 +28,7 @@ exports[`should render custom link 1`] = `
>
<a
href="http://example.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
http://example.com
@@ -51,6 +51,51 @@ exports[`should render custom link 1`] = `
</tr>
`;
+exports[`should render dangerous code as plain text 1`] = `
+<tr
+ data-name="dangerous"
+>
+ <td
+ className="nowrap"
+ >
+ <div>
+ <ProjectLinkIcon
+ className="little-spacer-right"
+ type="dangerous"
+ />
+ <div
+ className="display-inline-block text-top"
+ >
+ <span
+ className="js-name"
+ >
+ dangerous
+ </span>
+ </div>
+ </div>
+ </td>
+ <td
+ className="nowrap js-url"
+ >
+ javascript:alert("Hello")
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <ConfirmButton
+ confirmButtonText="delete"
+ confirmData="12"
+ isDestructive={true}
+ modalBody="project_links.are_you_sure_to_delete_x_link.dangerous"
+ modalHeader="project_links.delete_project_link"
+ onConfirm={[MockFunction]}
+ >
+ <Component />
+ </ConfirmButton>
+ </td>
+</tr>
+`;
+
exports[`should render provided link 1`] = `
<tr>
<td
@@ -88,7 +133,7 @@ exports[`should render provided link 1`] = `
>
<a
href="http://example.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
http://example.com