From b8d394da901488b5493e95c0bf98d0ff1139de09 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 3 Dec 2019 15:41:43 +0100 Subject: [PATCH] SONAR-12717 Security Hotspots Page --- .../sonar-web/public/images/hotspot-large.svg | 1 + .../src/main/js/api/securityHotspots.ts | 30 +++ .../nav/component/ComponentNavMenu.tsx | 13 + .../ComponentNavMenu-test.tsx.snap | 104 ++++++++ .../main/js/app/styles/components/page.css | 4 + .../src/main/js/app/utils/startReactApp.tsx | 7 + .../securityHotspots/SecurityHotspotsApp.tsx | 111 +++++++++ .../SecurityHotspotsAppRenderer.tsx | 97 ++++++++ .../__tests__/SecurityHotspotsApp-test.tsx | 79 +++++++ .../SecurityHotspotsAppRenderer-test.tsx | 47 ++++ .../SecurityHotspotsApp-test.tsx.snap | 10 + .../SecurityHotspotsAppRenderer-test.tsx.snap | 34 +++ .../securityHotspots/__tests__/utils-test.ts | 144 +++++++++++ .../securityHotspots/components/FilterBar.tsx | 30 +++ .../components/HotspotCategory.tsx | 79 +++++++ .../components/HotspotList.css | 104 ++++++++ .../components/HotspotList.tsx | 84 +++++++ .../components/HotspotListItem.tsx | 44 ++++ .../components/HotspotViewer.tsx | 30 +++ .../__tests__/HotspotCategory-test.tsx | 56 +++++ .../components/__tests__/HotspotList-test.tsx | 63 +++++ .../__tests__/HotspotListItem-test.tsx | 44 ++++ .../HotspotCategory-test.tsx.snap | 166 +++++++++++++ .../__snapshots__/HotspotList-test.tsx.snap | 223 ++++++++++++++++++ .../HotspotListItem-test.tsx.snap | 39 +++ .../main/js/apps/securityHotspots/styles.css | 51 ++++ .../main/js/apps/securityHotspots/utils.ts | 64 +++++ .../js/helpers/mocks/security-hotspots.ts | 39 +++ .../src/main/js/types/securityHotspots.ts | 48 ++++ .../resources/org/sonar/l10n/core.properties | 19 ++ 30 files changed, 1864 insertions(+) create mode 100644 server/sonar-web/public/images/hotspot-large.svg create mode 100644 server/sonar-web/src/main/js/api/securityHotspots.ts create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsApp.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/__tests__/utils-test.ts create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotCategory.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.css create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotListItem.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewer.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotCategory-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotList-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotListItem-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/styles.css create mode 100644 server/sonar-web/src/main/js/apps/securityHotspots/utils.ts create mode 100644 server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts create mode 100644 server/sonar-web/src/main/js/types/securityHotspots.ts diff --git a/server/sonar-web/public/images/hotspot-large.svg b/server/sonar-web/public/images/hotspot-large.svg new file mode 100644 index 00000000000..edfcb684a6c --- /dev/null +++ b/server/sonar-web/public/images/hotspot-large.svg @@ -0,0 +1 @@ + diff --git a/server/sonar-web/src/main/js/api/securityHotspots.ts b/server/sonar-web/src/main/js/api/securityHotspots.ts new file mode 100644 index 00000000000..f6a0ab8a64c --- /dev/null +++ b/server/sonar-web/src/main/js/api/securityHotspots.ts @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { getJSON } from 'sonar-ui-common/helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; +import { HotspotSearchResponse } from '../types/securityHotspots'; + +export function getSecurityHotspots(data: { + projectKey: string; + p: number; + ps: number; +}): Promise { + return getJSON('/api/hotspots/search', data).catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx index 6ad6d9f91a4..71258e7c207 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx @@ -151,6 +151,18 @@ export class ComponentNavMenu extends React.PureComponent { ); } + renderSecurityHotspotsLink() { + return ( +
  • + + {translate('layout.security_hotspots')} + +
  • + ); + } + renderSecurityReports() { const { branchLike, component } = this.props; const { extensions = [] } = component; @@ -488,6 +500,7 @@ export class ComponentNavMenu extends React.PureComponent { {this.renderDashboardLink()} {this.renderIssuesLink()} + {this.renderSecurityHotspotsLink()} {this.renderSecurityReports()} {this.renderComponentMeasuresLink()} {this.renderCodeLink()} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap index 696bb7739f1..1b30f2e863c 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap @@ -75,6 +75,24 @@ exports[`should work for a branch 1`] = ` issues.page +
  • + + layout.security_hotspots + +
  • +
  • + + layout.security_hotspots + +
  • +
  • + + layout.security_hotspots + +
  • +
  • + + layout.security_hotspots + +
  • +
  • + + layout.security_hotspots + +
  • +
  • + + layout.security_hotspots + +
  • + + import('../../apps/securityHotspots/SecurityHotspotsApp') + )} + /> ; + selectedHotspotKey: string | undefined; +} + +export default class SecurityHotspotsApp extends React.PureComponent { + mounted = false; + state = { + loading: true, + hotspots: [], + securityCategories: {}, + selectedHotspotKey: undefined + }; + + componentDidMount() { + this.mounted = true; + addNoFooterPageClass(); + this.fetchInitialData(); + } + + componentDidUpdate(previous: Props) { + if (this.props.component.key !== previous.component.key) { + this.fetchInitialData(); + } + } + + componentWillUnmount() { + removeNoFooterPageClass(); + this.mounted = false; + } + + fetchInitialData() { + return Promise.all([ + getStandards(), + getSecurityHotspots({ projectKey: this.props.component.key, p: 1, ps: PAGE_SIZE }) + ]) + .then(([{ sonarsourceSecurity }, response]) => { + if (!this.mounted) { + return; + } + + const hotspots = sortHotspots(response.hotspots, sonarsourceSecurity); + + this.setState({ + hotspots, + loading: false, + securityCategories: sonarsourceSecurity, + selectedHotspotKey: hotspots.length > 0 ? hotspots[0].key : undefined + }); + }) + .catch(() => { + if (this.mounted) { + this.setState({ loading: false }); + } + }); + } + + handleHotspotClick = (key: string) => this.setState({ selectedHotspotKey: key }); + + render() { + const { hotspots, loading, securityCategories, selectedHotspotKey } = this.state; + + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx new file mode 100644 index 00000000000..60dcbb1644b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx @@ -0,0 +1,97 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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'; +import { Helmet } from 'react-helmet-async'; +import { Link } from 'react-router'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; +import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget'; +import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; +import ScreenPositionHelper from '../../components/common/ScreenPositionHelper'; +import { RawHotspot } from '../../types/securityHotspots'; +import FilterBar from './components/FilterBar'; +import HotspotList from './components/HotspotList'; +import HotspotViewer from './components/HotspotViewer'; +import './styles.css'; + +export interface SecurityHotspotsAppRendererProps { + hotspots: RawHotspot[]; + loading: boolean; + onHotspotClick: (key: string) => void; + selectedHotspotKey?: string; + securityCategories: T.Dict<{ title: string; description?: string }>; +} + +export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRendererProps) { + const { hotspots, loading, securityCategories, selectedHotspotKey } = props; + return ( +
    + + + {({ top }) => ( +
    + + + + + + + {hotspots.length === 0 ? ( +
    + {translate('hotspots.page')} +

    {translate('hotspots.no_hotspots.title')}

    +
    + {translate('hotspots.no_hotspots.description')} +
    + + {translate('hotspots.learn_more')} + +
    + ) : ( +
    +
    + +
    +
    + +
    +
    + )} +
    +
    + )} +
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx new file mode 100644 index 00000000000..3cec33f41d7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { addNoFooterPageClass } from 'sonar-ui-common/helpers/pages'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { getSecurityHotspots } from '../../../api/securityHotspots'; +import { mockMainBranch } from '../../../helpers/mocks/branch-like'; +import { mockHotspot } from '../../../helpers/mocks/security-hotspots'; +import { getStandards } from '../../../helpers/security-standard'; +import { mockComponent } from '../../../helpers/testMocks'; +import SecurityHotspotsApp from '../SecurityHotspotsApp'; + +jest.mock('sonar-ui-common/helpers/pages', () => ({ + addNoFooterPageClass: jest.fn(), + removeNoFooterPageClass: jest.fn() +})); + +jest.mock('../../../api/securityHotspots', () => ({ + getSecurityHotspots: jest.fn().mockResolvedValue({ hotspots: [], rules: [] }) +})); + +jest.mock('../../../helpers/security-standard', () => ({ + getStandards: jest.fn() +})); + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should load data correctly', async () => { + const sonarsourceSecurity = { cat1: { title: 'cat 1' } }; + (getStandards as jest.Mock).mockResolvedValue({ sonarsourceSecurity }); + + const hotspots = [mockHotspot()]; + (getSecurityHotspots as jest.Mock).mockResolvedValue({ + hotspots + }); + + const wrapper = shallowRender(); + + expect(wrapper.state().loading).toBe(true); + + expect(addNoFooterPageClass).toBeCalled(); + expect(getStandards).toBeCalled(); + expect(getSecurityHotspots).toBeCalled(); + + await waitAndUpdate(wrapper); + + expect(wrapper.state().loading).toBe(false); + expect(wrapper.state().hotspots).toEqual(hotspots); + expect(wrapper.state().selectedHotspotKey).toBe(hotspots[0].key); + expect(wrapper.state().securityCategories).toBe(sonarsourceSecurity); + + expect(wrapper.state()); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx new file mode 100644 index 00000000000..f3a3f832a20 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockHotspot } from '../../../helpers/mocks/security-hotspots'; +import SecurityHotspotsAppRenderer, { + SecurityHotspotsAppRendererProps +} from '../SecurityHotspotsAppRenderer'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should render correctly with hotspots', () => { + const hotspots = [mockHotspot({ key: 'h1' }), mockHotspot({ key: 'h2' })]; + expect(shallowRender({ hotspots })).toMatchSnapshot(); + expect(shallowRender({ hotspots, selectedHotspotKey: 'h2' })).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap new file mode 100644 index 00000000000..4f83ea76cd3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap new file mode 100644 index 00000000000..67d90c34884 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
    + + + + +
    +`; + +exports[`should render correctly with hotspots 1`] = ` +
    + + + + +
    +`; + +exports[`should render correctly with hotspots 2`] = ` +
    + + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/utils-test.ts new file mode 100644 index 00000000000..1221e827d3f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/utils-test.ts @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { mockHotspot } from '../../../helpers/mocks/security-hotspots'; +import { RiskExposure } from '../../../types/securityHotspots'; +import { groupByCategory, mapRules, sortHotspots } from '../utils'; + +const hotspots = [ + mockHotspot({ + key: '3', + vulnerabilityProbability: RiskExposure.HIGH, + securityCategory: 'object-injection', + message: 'tfdh' + }), + mockHotspot({ + key: '5', + vulnerabilityProbability: RiskExposure.MEDIUM, + securityCategory: 'xpath-injection', + message: 'asdf' + }), + mockHotspot({ + key: '1', + vulnerabilityProbability: RiskExposure.HIGH, + securityCategory: 'dos', + message: 'a' + }), + mockHotspot({ + key: '7', + vulnerabilityProbability: RiskExposure.LOW, + securityCategory: 'ssrf', + message: 'rrrr' + }), + mockHotspot({ + key: '2', + vulnerabilityProbability: RiskExposure.HIGH, + securityCategory: 'dos', + message: 'b' + }), + mockHotspot({ + key: '8', + vulnerabilityProbability: RiskExposure.LOW, + securityCategory: 'ssrf', + message: 'sssss' + }), + mockHotspot({ + key: '4', + vulnerabilityProbability: RiskExposure.MEDIUM, + securityCategory: 'log-injection', + message: 'asdf' + }), + mockHotspot({ + key: '9', + vulnerabilityProbability: RiskExposure.LOW, + securityCategory: 'xxe', + message: 'aaa' + }), + mockHotspot({ + key: '6', + vulnerabilityProbability: RiskExposure.LOW, + securityCategory: 'xss', + message: 'zzz' + }) +]; + +const categories = { + 'object-injection': { + title: 'Object Injection' + }, + 'xpath-injection': { + title: 'XPath Injection' + }, + 'log-injection': { + title: 'Log Injection' + }, + dos: { + title: 'Denial of Service (DoS)' + }, + ssrf: { + title: 'Server-Side Request Forgery (SSRF)' + }, + xxe: { + title: 'XML External Entity (XXE)' + }, + xss: { + title: 'Cross-Site Scripting (XSS)' + } +}; + +describe('sortHotspots', () => { + it('should sort properly', () => { + const result = sortHotspots(hotspots, categories); + + expect(result.map(h => h.key)).toEqual(['1', '2', '3', '4', '5', '6', '7', '8', '9']); + }); +}); + +describe('groupByCategory', () => { + it('should group and sort properly', () => { + const result = groupByCategory(hotspots, categories); + + expect(result).toHaveLength(7); + expect(result.map(g => g.key)).toEqual([ + 'xss', + 'dos', + 'log-injection', + 'object-injection', + 'ssrf', + 'xxe', + 'xpath-injection' + ]); + }); +}); + +describe('mapRules', () => { + it('should map names to keys', () => { + const rules = [ + { key: 'a', name: 'A rule' }, + { key: 'b', name: 'B rule' }, + { key: 'c', name: 'C rule' } + ]; + + expect(mapRules(rules)).toEqual({ + a: 'A rule', + b: 'B rule', + c: 'C rule' + }); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx new file mode 100644 index 00000000000..0927843ed84 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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'; + +export interface FilterBarProps {} + +export default function FilterBar(props: FilterBarProps) { + return ( +
    +

    Filter

    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotCategory.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotCategory.tsx new file mode 100644 index 00000000000..ee9109cee46 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotCategory.tsx @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 classNames from 'classnames'; +import * as React from 'react'; +import ChevronDownIcon from 'sonar-ui-common/components/icons/ChevronDownIcon'; +import ChevronUpIcon from 'sonar-ui-common/components/icons/ChevronUpIcon'; +import { RawHotspot } from '../../../types/securityHotspots'; +import HotspotListItem from './HotspotListItem'; + +export interface HotspotCategoryProps { + category: { + key: string; + title: string; + }; + hotspots: RawHotspot[]; + onHotspotClick: (key: string) => void; + selectedHotspotKey: string | undefined; +} + +export default function HotspotCategory(props: HotspotCategoryProps) { + const { category, hotspots, selectedHotspotKey } = props; + + const [expanded, setExpanded] = React.useState(true); + + if (hotspots.length < 1) { + return null; + } + + const risk = hotspots[0].vulnerabilityProbability; + + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.css b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.css new file mode 100644 index 00000000000..e8f6972ca43 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.css @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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. + */ +.hotspot-list-header { + padding: calc(2 * var(--gridSize)) var(--gridSize); +} + +.hotspot-risk-header { + padding: var(--gridSize); +} + +.hotspot-category { + background-color: white; + border: 1px solid var(--barBorderColor); +} + +.hotspot-category .hotspot-category-header { + padding: calc(2 * var(--gridSize)) var(--gridSize); + color: var(--baseFontColor); + border-bottom: none; + border-left: 4px solid; +} + +.hotspot-category .hotspot-category-header:hover { + color: var(--blue); +} + +.hotspot-category.HIGH .hotspot-category-header { + border-left-color: var(--red); +} + +.hotspot-category.MEDIUM .hotspot-category-header { + border-left-color: var(--orange); +} + +.hotspot-category.LOW .hotspot-category-header { + border-left-color: var(--yellow); +} + +.hotspot-item { + color: var(--baseFontColor); + display: block; + padding: var(--gridSize) calc(2 * var(--gridSize)); + border: 1px solid transparent; + border-top-color: var(--barBorderColor); + transition: padding 0s, border 0s; +} + +.hotspot-item:hover { + background-color: var(--veryLightBlue); + border: 1px dashed var(--blue); + color: var(--baseFontColor); +} + +.hotspot-item.highlight { + background-color: var(--veryLightBlue); + color: var(--baseFontColor); + border: 1px solid var(--blue); + cursor: unset; +} + +.hotspot-counter { + color: var(--baseFontColor); + background-color: var(--gray94); + border-radius: 50%; + padding: calc(var(--gridSize) / 2) var(--gridSize); +} + +.hotspot-risk-badge { + color: white; + text-transform: uppercase; + display: inline-block; + text-align: center; + min-width: 48px; + padding: 0 var(--gridSize); + font-weight: bold; + border-radius: 2px; +} + +.hotspot-risk-badge.HIGH { + background-color: var(--red); +} +.hotspot-risk-badge.MEDIUM { + background-color: var(--orange); +} +.hotspot-risk-badge.LOW { + background-color: var(--yellow); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.tsx new file mode 100644 index 00000000000..a33c3b84c35 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotList.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 classNames from 'classnames'; +import { groupBy } from 'lodash'; +import * as React from 'react'; +import SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { RawHotspot, RiskExposure } from '../../../types/securityHotspots'; +import { groupByCategory, RISK_EXPOSURE_LEVELS } from '../utils'; +import HotspotCategory from './HotspotCategory'; +import './HotspotList.css'; + +export interface HotspotListProps { + hotspots: RawHotspot[]; + onHotspotClick: (key: string) => void; + securityCategories: T.Dict<{ title: string; description?: string }>; + selectedHotspotKey: string | undefined; +} + +export default function HotspotList(props: HotspotListProps) { + const { hotspots, securityCategories, selectedHotspotKey } = props; + + const groupedHotspots: Array<{ + risk: RiskExposure; + categories: Array<{ key: string; hotspots: RawHotspot[]; title: string }>; + }> = React.useMemo(() => { + const risks = groupBy(hotspots, h => h.vulnerabilityProbability); + + return RISK_EXPOSURE_LEVELS.map(risk => ({ + risk, + categories: groupByCategory(risks[risk], securityCategories) + })).filter(risk => risk.categories.length > 0); + }, [hotspots, securityCategories]); + + return ( + <> +

    + + {translateWithParameters(`hotspots.list_title.TO_REVIEW`, hotspots.length)} +

    +
      + {groupedHotspots.map(riskGroup => ( +
    • +
      + {translate('hotspots.risk_exposure')} +
      + {translate('risk_exposure', riskGroup.risk)} +
      +
      +
        + {riskGroup.categories.map(cat => ( +
      • + +
      • + ))} +
      +
    • + ))} +
    + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotListItem.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotListItem.tsx new file mode 100644 index 00000000000..549c8e35bba --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotListItem.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 classNames from 'classnames'; +import * as React from 'react'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { RawHotspot } from '../../../types/securityHotspots'; + +export interface HotspotListItemProps { + hotspot: RawHotspot; + onClick: (key: string) => void; + selected: boolean; +} + +export function HotspotListItem(props: HotspotListItemProps) { + const { hotspot, selected } = props; + return ( + !selected && props.onClick(hotspot.key)}> +
    {hotspot.message}
    +
    {translate('issue.status', hotspot.status)}
    +
    + ); +} + +export default React.memo(HotspotListItem); diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewer.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewer.tsx new file mode 100644 index 00000000000..c4e8c911f98 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewer.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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'; + +export interface Props {} + +export default function HotspotViewer(props: Props) { + return ( +
    + Show hotspot details +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotCategory-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotCategory-test.tsx new file mode 100644 index 00000000000..f1d3312579c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotCategory-test.tsx @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; +import HotspotCategory, { HotspotCategoryProps } from '../HotspotCategory'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should render correctly with hotspots', () => { + const hotspots = [mockHotspot({ key: 'h1' }), mockHotspot({ key: 'h2' })]; + expect(shallowRender({ hotspots })).toMatchSnapshot(); +}); + +it('should handle collapse and expand', () => { + const wrapper = shallowRender({ hotspots: [mockHotspot()] }); + + wrapper.find('.hotspot-category-header').simulate('click'); + + expect(wrapper).toMatchSnapshot(); + + wrapper.find('.hotspot-category-header').simulate('click'); + + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotList-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotList-test.tsx new file mode 100644 index 00000000000..b48e336cd19 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotList-test.tsx @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; +import { RiskExposure } from '../../../../types/securityHotspots'; +import HotspotList, { HotspotListProps } from '../HotspotList'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should render correctly with hotspots', () => { + const hotspots = [ + mockHotspot({ key: 'h1', securityCategory: 'cat2' }), + mockHotspot({ key: 'h2', securityCategory: 'cat1' }), + mockHotspot({ + key: 'h3', + securityCategory: 'cat1', + vulnerabilityProbability: RiskExposure.MEDIUM + }), + mockHotspot({ + key: 'h4', + securityCategory: 'cat1', + vulnerabilityProbability: RiskExposure.MEDIUM + }), + mockHotspot({ + key: 'h5', + securityCategory: 'cat2', + vulnerabilityProbability: RiskExposure.MEDIUM + }) + ]; + expect(shallowRender({ hotspots })).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotListItem-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotListItem-test.tsx new file mode 100644 index 00000000000..e9a7b7d0082 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotListItem-test.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; +import { HotspotListItem, HotspotListItemProps } from '../HotspotListItem'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ selected: true })).toMatchSnapshot(); +}); + +it('should handle click', () => { + const hotspot = mockHotspot({ key: 'hotspotKey' }); + const onClick = jest.fn(); + const wrapper = shallowRender({ hotspot, onClick }); + + wrapper.simulate('click'); + + expect(onClick).toBeCalledWith(hotspot.key); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap new file mode 100644 index 00000000000..3f07388c5ad --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap @@ -0,0 +1,166 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should handle collapse and expand 1`] = ` + +`; + +exports[`should handle collapse and expand 2`] = ` + +`; + +exports[`should render correctly 1`] = `""`; + +exports[`should render correctly with hotspots 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap new file mode 100644 index 00000000000..68c4b07fa9b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap @@ -0,0 +1,223 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +

    + + hotspots.list_title.TO_REVIEW.0 +

    +
      + +`; + +exports[`should render correctly with hotspots 1`] = ` + +

      + + hotspots.list_title.TO_REVIEW.5 +

      +
        +
      • +
        + + hotspots.risk_exposure + +
        + risk_exposure.HIGH +
        +
        +
          +
        • + +
        • +
        • + +
        • +
        +
      • +
      • +
        + + hotspots.risk_exposure + +
        + risk_exposure.MEDIUM +
        +
        +
          +
        • + +
        • +
        • + +
        • +
        +
      • +
      +
      +`; diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap new file mode 100644 index 00000000000..457deefac03 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +
      + '3' is a magic number. +
      +
      + issue.status.RESOLVED +
      +
      +`; + +exports[`should render correctly 2`] = ` + +
      + '3' is a magic number. +
      +
      + issue.status.RESOLVED +
      +
      +`; diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/styles.css b/server/sonar-web/src/main/js/apps/securityHotspots/styles.css new file mode 100644 index 00000000000..b76b508053d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/styles.css @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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. + */ +#security_hotspots .wrapper { + position: fixed; + /* top is defined programatically */ + bottom: 0; + width: 100%; +} + +#security_hotspots .layout-page { + margin: 0 auto; + min-width: var(--minPageWidth); + max-width: 1280px; + height: 100%; +} + +#security_hotspots .filter-bar { + max-width: 1280px; + margin: 0 auto; + padding: var(--gridSize) 20px; + border-bottom: 1px solid var(--barBorderColor); +} + +#security_hotspots .sidebar { + flex: 1 0 30%; + border-right: 1px solid var(--barBorderColor); + height: 100%; + overflow-y: auto; +} + +#security_hotspots .main { + flex: 1 0 70%; + overflow-y: auto; +} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/utils.ts b/server/sonar-web/src/main/js/apps/securityHotspots/utils.ts new file mode 100644 index 00000000000..147588deaa0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/securityHotspots/utils.ts @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { groupBy, sortBy } from 'lodash'; +import { RawHotspot, RiskExposure } from '../../types/securityHotspots'; + +export const RISK_EXPOSURE_LEVELS = [RiskExposure.HIGH, RiskExposure.MEDIUM, RiskExposure.LOW]; + +export function mapRules(rules: Array<{ key: string; name: string }>): T.Dict { + return rules.reduce((ruleMap: T.Dict, r) => { + ruleMap[r.key] = r.name; + return ruleMap; + }, {}); +} + +export function groupByCategory( + hotspots: RawHotspot[] = [], + securityCategories: T.Dict<{ title: string; description?: string }> +) { + const groups = groupBy(hotspots, h => h.securityCategory); + + return sortBy( + Object.keys(groups).map(key => ({ + key, + title: getCategoryTitle(key, securityCategories), + hotspots: groups[key] + })), + cat => cat.title + ); +} + +export function sortHotspots( + hotspots: RawHotspot[], + securityCategories: T.Dict<{ title: string }> +) { + return sortBy(hotspots, [ + h => RISK_EXPOSURE_LEVELS.indexOf(h.vulnerabilityProbability), + h => getCategoryTitle(h.securityCategory, securityCategories), + h => h.message + ]); +} + +function getCategoryTitle( + key: string, + securityCategories: T.Dict<{ title: string; description?: string }> +) { + return securityCategories[key] ? securityCategories[key].title : key; +} diff --git a/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts b/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts new file mode 100644 index 00000000000..f1c4be442c9 --- /dev/null +++ b/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { RawHotspot, RiskExposure } from '../../types/securityHotspots'; + +export function mockHotspot(overrides: Partial = {}): RawHotspot { + return { + key: '01fc972e-2a3c-433e-bcae-0bd7f88f5123', + component: 'com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest', + project: 'com.github.kevinsawicki:http-request', + rule: 'checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck', + status: 'RESOLVED', + resolution: 'FALSE-POSITIVE', + securityCategory: 'command-injection', + vulnerabilityProbability: RiskExposure.HIGH, + message: "'3' is a magic number.", + line: 81, + author: 'Developer 1', + creationDate: '2013-05-13T17:55:39+0200', + updateDate: '2013-05-13T17:55:39+0200', + ...overrides + }; +} diff --git a/server/sonar-web/src/main/js/types/securityHotspots.ts b/server/sonar-web/src/main/js/types/securityHotspots.ts new file mode 100644 index 00000000000..dc95ad98d43 --- /dev/null +++ b/server/sonar-web/src/main/js/types/securityHotspots.ts @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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. + */ +export enum RiskExposure { + LOW = 'LOW', + MEDIUM = 'MEDIUM', + HIGH = 'HIGH' +} + +export interface RawHotspot { + assignee?: string; + author?: string; + component: string; + creationDate: string; + key: string; + line?: number; + message: string; + project: string; + resolution: string; + rule: string; + securityCategory: string; + updateDate: string; + vulnerabilityProbability: RiskExposure; + status: string; + subProject?: string; +} + +export interface HotspotSearchResponse { + components?: { key: string; qualifier: string; name: string }[]; + hotspots: RawHotspot[]; + paging: T.Paging; +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index cd5465cece5..34eacaf3ec6 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -473,6 +473,7 @@ layout.login=Log in layout.logout=Log out layout.measures=Measures layout.settings=Administration +layout.security_hotspots=Security Hotspots layout.security_reports=Security Reports layout.sonar.slogan=Continuous Code Quality @@ -633,6 +634,24 @@ sessions.email_already_exists.4=Your email address will be erased from the first sessions.email_already_exists.5=You will no longer receive email notifications from this account. sessions.email_already_exists.6=Issues won't be automatically assigned to this account anymore. +#------------------------------------------------------------------------------ +# +# HOTSPOTS +# +#------------------------------------------------------------------------------ + +risk_exposure.HIGH=High +risk_exposure.MEDIUM=Medium +risk_exposure.LOW=Low + +hotspots.page=Security Hotspots +hotspots.no_hotspots.title=There are no Security Hotspots to review +hotspots.no_hotspots.description=Next time you analyse a piece of code that contains a potential security risk, it will show up here. +hotspots.learn_more=Learn more about Security Hotspots +hotspots.list_title.TO_REVIEW={0} Security Hotspots to review +hotspots.list_title.REVIEWED={0} reviewed Security Hotspots +hotspots.risk_exposure=Review priority: + #------------------------------------------------------------------------------ # # ISSUES -- 2.39.5