diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2018-12-11 08:35:04 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2018-12-20 11:41:28 +0100 |
commit | 65e616ffa93f0e0029f792780ff8f3db0b830c2d (patch) | |
tree | a7495b271e05e44c328f19f1cc23689bfa6b3452 /server/sonar-web/src/main/js/apps | |
parent | 3a08c1730f3fe248414add8fa57a61c5670e04c9 (diff) | |
download | sonarqube-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')
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 |