From 65e616ffa93f0e0029f792780ff8f3db0b830c2d Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 11 Dec 2018 08:35:04 +0100 Subject: [PATCH] SONAR-11506, SSF-62 Handle XSS code in project links --- .../src/main/js/app/utils/isValidUri.ts | 24 ++++++++ .../main/js/apps/overview/meta/MetaLink.tsx | 55 ++++++++++++++++--- .../overview/meta/__tests__/MetaLink-test.tsx | 11 ++++ .../__snapshots__/MetaLink-test.tsx.snap | 26 +++++++-- .../src/main/js/apps/overview/styles.css | 17 ++++++ .../src/main/js/apps/projectLinks/LinkRow.tsx | 11 +++- .../projectLinks/__tests__/LinkRow-test.tsx | 11 ++++ .../__snapshots__/LinkRow-test.tsx.snap | 49 ++++++++++++++++- 8 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/utils/isValidUri.ts diff --git a/server/sonar-web/src/main/js/app/utils/isValidUri.ts b/server/sonar-web/src/main/js/app/utils/isValidUri.ts new file mode 100644 index 00000000000..115eaf60168 --- /dev/null +++ b/server/sonar-web/src/main/js/app/utils/isValidUri.ts @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { isWebUri } from 'valid-url'; + +export default function(url: string): boolean { + return /^(\/|scm:)/.test(url) || !!isWebUri(url); +} 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 ( -
  • - - - {getLinkName(link)} - -
  • - ); +interface State { + expanded: boolean; +} + +export default class MetaLink extends React.PureComponent { + state = { + expanded: false + }; + + handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + this.setState(s => ({ expanded: !s.expanded })); + }; + + render() { + const { link } = this.props; + return ( +
  • + + + {getLinkName(link)} + + {this.state.expanded && ( +
    + ) => event.currentTarget.select()} + readOnly={true} + type="text" + value={link.url} + /> + + + +
    + )} +
  • + ); + } } 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()).toMatchSnapshot(); }); +it('should render dangerous links as plaintext', () => { + const link = { + id: '1', + name: 'Dangerous', + url: 'javascript:alert("hi")', + type: 'dangerous' + }; + + expect(shallow()).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`] = ` `; + +exports[`should render dangerous links as plaintext 1`] = ` +
  • + + + Dangerous + +
  • +`; 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 { {this.renderName(link)} - - {link.url} - + {isValidUri(link.url) ? ( + + {link.url} + + ) : ( + link.url + )} {this.renderDeleteButton(link)} 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( + + ) + ).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`] = ` > http://example.com @@ -51,6 +51,51 @@ exports[`should render custom link 1`] = ` `; +exports[`should render dangerous code as plain text 1`] = ` + + +
    + +
    + + dangerous + +
    +
    + + + javascript:alert("Hello") + + + + + + + +`; + exports[`should render provided link 1`] = `
    http://example.com -- 2.39.5