From 29ccfe5d20dfdb1e71b53b23a6077f3235d4fd6e Mon Sep 17 00:00:00 2001 From: Wouter Admiraal <45544358+wouter-admiraal-sonarsource@users.noreply.github.com> Date: Fri, 29 Mar 2019 09:09:29 +0100 Subject: [PATCH] SONAR-11867, SSF-74 Fix XSS in project links on account/projects (#3203) --- .../js/apps/account/projects/ProjectCard.tsx | 37 ++++++++++++------- .../projects/__tests__/ProjectCard-test.js | 2 +- .../main/js/apps/overview/meta/MetaLink.d.ts | 34 +++++++++++++++++ .../main/js/apps/overview/meta/MetaLink.js | 9 +++-- .../overview/meta/__tests__/MetaLink-test.js | 1 + .../__snapshots__/MetaLink-test.js.snap | 32 ++++++++++++---- .../main/js/apps/project-admin/links/utils.js | 2 +- 7 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/overview/meta/MetaLink.d.ts diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx index cfa10ff8e37..a0d2959306a 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx @@ -18,22 +18,42 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { sortBy } from 'lodash'; import { Link } from 'react-router'; import { Project } from './types'; import DateFromNow from '../../../components/intl/DateFromNow'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import Level from '../../../components/ui/Level'; import Tooltip from '../../../components/controls/Tooltip'; +import MetaLink from '../../overview/meta/MetaLink'; +import { orderLinks } from '../../project-admin/links/utils'; import { translateWithParameters, translate } from '../../../helpers/l10n'; interface Props { project: Project; } +type ProjectLink = { + id: string; + name: string; + url: string; + type: string; +}; + export default function ProjectCard({ project }: Props) { const isAnalyzed = project.lastAnalysisDate != null; - const links = sortBy(project.links, 'type'); + + const { links } = project; + const orderedLinks: ProjectLink[] = orderLinks( + links.map((link, i) => { + const { href, name, type } = link; + return { + id: `link-${i}`, + name, + type, + url: href + }; + }) + ); return (
@@ -70,17 +90,8 @@ export default function ProjectCard({ project }: Props) { {links.length > 0 && (
diff --git a/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js b/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js index c714da617dc..997c6acd505 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js +++ b/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js @@ -82,5 +82,5 @@ it('should render links', () => { links: [{ name: 'n', type: 't', href: 'h' }] }; const output = shallow(); - expect(output.find('.account-project-links').find('li').length).toBe(1); + expect(output.find('MetaLink').length).toBe(1); }); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.d.ts b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.d.ts new file mode 100644 index 00000000000..963030cdb27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.d.ts @@ -0,0 +1,34 @@ +/* + * 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 * as React from 'react'; + +type Link = { + id: string; + name: string; + url: string; + type: string; +}; + +interface Props { + iconOnly?: boolean; + link: Link; +} + +export default class MetaLink extends React.Component {} diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js index 2f368484c9b..08d1c4db53c 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js @@ -39,6 +39,7 @@ type State = {| export default class MetaLink extends React.PureComponent { /*:: props: { + iconOnly?: boolean, link: Link }; */ @@ -66,7 +67,7 @@ export default class MetaLink extends React.PureComponent { } render() { - const { link } = this.props; + const { iconOnly, link } = this.props; return (
  • @@ -74,10 +75,10 @@ export default class MetaLink extends React.PureComponent { className="link-with-icon" href={link.url} target="_blank" - onClick={!isClickable(link) && this.handleClick}> + onClick={!isClickable(link) && this.handleClick} + title={link.name}> {this.renderLinkIcon(link)} -   - {link.name} + {!iconOnly && `\u00A0${link.name}`} {this.state.expanded && (
    diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js index 2499c2e3c36..3af59d19e93 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js @@ -31,6 +31,7 @@ it('should match snapshot', () => { }; expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); it('should expand and collapse link', () => { diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap index 416a8104ae2..931f9d3f9c1 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap @@ -7,12 +7,12 @@ exports[`should expand and collapse link 1`] = ` href="scm:git:git@github.com" onClick={[Function]} target="_blank" + title="Foo" > -   - Foo +  Foo
  • `; @@ -24,12 +24,12 @@ exports[`should expand and collapse link 2`] = ` href="scm:git:git@github.com" onClick={[Function]} target="_blank" + title="Foo" > -   - Foo +  Foo
    -   - Foo +  Foo `; @@ -69,12 +69,28 @@ exports[`should match snapshot 1`] = ` href="http://example.com" onClick={false} target="_blank" + title="Foo" + > + +  Foo + + +`; + +exports[`should match snapshot 2`] = ` +
  • + -   - Foo
  • `; diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/utils.js b/server/sonar-web/src/main/js/apps/project-admin/links/utils.js index 9ae5e6ef3d7..c8772b917cc 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/utils.js +++ b/server/sonar-web/src/main/js/apps/project-admin/links/utils.js @@ -29,7 +29,7 @@ export function orderLinks(links) { const [provided, unknown] = partition(links, isProvided); return [ ...sortBy(provided, link => PROVIDED_TYPES.indexOf(link.type)), - ...sortBy(unknown, link => link.name.toLowerCase()) + ...sortBy(unknown, link => link.name && link.name.toLowerCase()) ]; } -- 2.39.5