aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-docs/src/tooltips/security-reports/cwe.md1
-rw-r--r--server/sonar-web/src/main/js/api/security-reports.ts31
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx41
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap350
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/tables.css5
-rw-r--r--server/sonar-web/src/main/js/app/types.ts11
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.js4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx67
-rwxr-xr-xserver/sonar-web/src/main/js/apps/securityReports/components/App.tsx158
-rwxr-xr-xserver/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx209
-rw-r--r--server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx155
-rw-r--r--server/sonar-web/src/main/js/apps/securityReports/components/__tests__/VulnerabilityList-test.tsx82
-rw-r--r--server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap394
-rw-r--r--server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/VulnerabilityList-test.tsx.snap744
-rw-r--r--server/sonar-web/src/main/js/apps/securityReports/style.css26
-rwxr-xr-xserver/sonar-web/src/main/js/apps/securityReports/utils.ts51
-rw-r--r--server/sonar-web/src/main/js/components/controls/Checkbox.tsx2
-rw-r--r--server/sonar-web/src/main/js/helpers/standards.json8
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties18
19 files changed, 2306 insertions, 51 deletions
diff --git a/server/sonar-docs/src/tooltips/security-reports/cwe.md b/server/sonar-docs/src/tooltips/security-reports/cwe.md
new file mode 100644
index 00000000000..313e023c8eb
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/security-reports/cwe.md
@@ -0,0 +1 @@
+CWE™ is a community-developed list of common software security weaknesses. It serves as a common language, a measuring stick for software security tools, and as a baseline for weakness identification, mitigation, and prevention efforts. \ No newline at end of file
diff --git a/server/sonar-web/src/main/js/api/security-reports.ts b/server/sonar-web/src/main/js/api/security-reports.ts
new file mode 100644
index 00000000000..2747ea7536f
--- /dev/null
+++ b/server/sonar-web/src/main/js/api/security-reports.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { SecurityHotspot } from '../app/types';
+import { getJSON } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
+
+export function getSecurityHotspots(data: {
+ project: string;
+ standard: 'owaspTop10' | 'sansTop25' | 'cwe';
+ includeDistribution?: boolean;
+ branch?: string;
+}): Promise<{ categories: Array<SecurityHotspot> }> {
+ return getJSON('/api/security_reports/show', 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 53009cc4d40..ca3a9ce6f5f 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
@@ -169,6 +169,46 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
);
}
+ renderSecurityReportsLink() {
+ return (
+ <ul className="menu">
+ <li>
+ <Link
+ activeClassName="active"
+ to={{ pathname: '/project/security_reports/owasp_top_10', query: this.getQuery() }}>
+ {translate('security_reports.owaspTop10.page')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ to={{ pathname: '/project/security_reports/sans_top_25', query: this.getQuery() }}>
+ {translate('security_reports.sansTop25.page')}
+ </Link>
+ </li>
+ </ul>
+ );
+ }
+
+ renderSecurityReports() {
+ const isActive = location.pathname.startsWith('/project/security_reports');
+ return (
+ <Dropdown overlay={this.renderSecurityReportsLink()} tagName="li">
+ {({ onToggleClick, open }) => (
+ <a
+ aria-expanded={String(open)}
+ aria-haspopup="true"
+ className={classNames('dropdown-toggle', { active: isActive || open })}
+ href="#"
+ onClick={onToggleClick}>
+ {translate('layout.security_reports')}
+ <DropdownIcon className="little-spacer-left" />
+ </a>
+ )}
+ </Dropdown>
+ );
+ }
+
renderAdministration() {
const { branchLike } = this.props;
@@ -450,6 +490,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
<NavBarTabs>
{this.renderDashboardLink()}
{this.renderIssuesLink()}
+ {this.renderSecurityReports()}
{this.renderComponentMeasuresLink()}
{this.renderCodeLink()}
{this.renderActivityLink()}
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 a62c3ce9e0e..40cbe0f76bb 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
@@ -38,6 +38,49 @@ exports[`should work for all qualifiers 1`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -208,6 +251,49 @@ exports[`should work for all qualifiers 2`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -327,6 +413,49 @@ exports[`should work for all qualifiers 3`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -446,6 +575,49 @@ exports[`should work for all qualifiers 4`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -538,6 +710,49 @@ exports[`should work for all qualifiers 5`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -659,6 +874,51 @@ exports[`should work for long-living branches 1`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "branch": "release",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "branch": "release",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -756,6 +1016,51 @@ exports[`should work for long-living branches 2`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "branch": "release",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "branch": "release",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
@@ -835,6 +1140,51 @@ exports[`should work for short-living branches 1`] = `
issues.page
</Link>
</li>
+ <Dropdown
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/owasp_top_10",
+ "query": Object {
+ "branch": "feature",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.owaspTop10.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/security_reports/sans_top_25",
+ "query": Object {
+ "branch": "feature",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ security_reports.sansTop25.page
+ </Link>
+ </li>
+ </ul>
+ }
+ tagName="li"
+ />
<li>
<Link
activeClassName="active"
diff --git a/server/sonar-web/src/main/js/app/styles/init/tables.css b/server/sonar-web/src/main/js/app/styles/init/tables.css
index 2a2a29366bb..880cfc80bb1 100644
--- a/server/sonar-web/src/main/js/app/styles/init/tables.css
+++ b/server/sonar-web/src/main/js/app/styles/init/tables.css
@@ -134,6 +134,11 @@ table.data.condensed > tbody > tr > td {
padding-bottom: 5px;
}
+table.data tr.subheader th {
+ font-size: var(--smallFontSize);
+ border-bottom: none;
+}
+
table.data.no-outer-padding > thead > tr > th:first-child {
padding-left: 0;
}
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index b546c8a212f..741521d8396 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -213,6 +213,17 @@ export function isSameHomePage(a: HomePage, b: HomePage) {
);
}
+export interface SecurityHotspot {
+ category?: string;
+ cwe?: string;
+ distribution?: Array<SecurityHotspot>;
+ openSecurityHotspots: number;
+ toReviewSecurityHotspots: number;
+ vulnerabilities: number;
+ vulnerabilityRating?: number;
+ wontFixSecurityHotspots: number;
+}
+
export interface Issue {
actions?: string[];
assignee?: string;
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js
index 97e9845a344..69602ff1e13 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.js
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js
@@ -198,6 +198,10 @@ const startReactApp = (lang, currentUser, appState) => {
)}
/>
<Route path="project/issues" component={Issues} />
+ <Route
+ path="project/security_reports/:type"
+ component={lazyLoad(() => import('../../apps/securityReports/components/App'))}
+ />
<Route path="project/quality_gate" childRoutes={projectQualityGateRoutes} />
<Route
path="project/quality_profiles"
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx
index 33d6d0e6f66..f305748e479 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx
@@ -26,6 +26,12 @@ import { translate } from '../../../helpers/l10n';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import FacetItem from '../../../components/facet/FacetItem';
import Select from '../../../components/controls/Select';
+import {
+ renderOwaspTop10Category,
+ renderSansTop25Category,
+ renderCWECategory,
+ Standards
+} from '../../securityReports/utils';
export interface Props {
cwe: string[];
@@ -43,12 +49,6 @@ export interface Props {
sansTop25Stats: { [x: string]: number } | undefined;
}
-interface Standards {
- owaspTop10: { [x: string]: { title: string } };
- sansTop25: { [x: string]: { title: string } };
- cwe: { [x: string]: { title: string } };
-}
-
interface State {
standards: Standards;
}
@@ -90,9 +90,9 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
getValues = () => {
return [
- ...this.props.owaspTop10.map(this.renderOwaspTop10Category),
- ...this.props.sansTop25.map(this.renderSansTop25Category),
- ...this.props.cwe.map(this.renderCWECategory)
+ ...this.props.owaspTop10.map(item => renderOwaspTop10Category(this.state.standards, item)),
+ ...this.props.sansTop25.map(item => renderSansTop25Category(this.state.standards, item)),
+ ...this.props.cwe.map(item => renderCWECategory(this.state.standards, item))
];
};
@@ -150,37 +150,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
this.handleItemClick('cwe', value, true);
};
- renderOwaspTop10Category = (category: string) => {
- const record = this.state.standards.owaspTop10[category];
- if (!record) {
- return category.toUpperCase();
- } else if (category === 'unknown') {
- return record.title;
- } else {
- return `${category.toUpperCase()} - ${record.title}`;
- }
- };
-
- renderCWECategory = (category: string) => {
- const record = this.state.standards.cwe[category];
- if (!record) {
- return `CWE-${category}`;
- } else if (category === 'unknown') {
- return record.title;
- } else {
- return `CWE-${category} - ${record.title}`;
- }
- };
-
- renderSansTop25Category = (category: string) => {
- const record = this.state.standards.sansTop25[category];
- return record ? record.title : category;
- };
-
renderList = (
statsProp: 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats',
valuesProp: 'owaspTop10' | 'cwe' | 'sansTop25',
- renderName: (category: string) => string,
+ renderName: (standards: Standards, category: string) => string,
onClick: (x: string, multiple?: boolean) => void
) => {
const stats = this.props[statsProp];
@@ -211,7 +184,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
active={values.includes(category)}
key={category}
loading={this.props.loading}
- name={renderName(category)}
+ name={renderName(this.state.standards, category)}
onClick={onClick}
stat={formatFacetStat(getStat(category))}
tooltip={values.length === 1 && !values.includes(category)}
@@ -226,18 +199,18 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
return this.renderList(
'owaspTop10Stats',
'owaspTop10',
- this.renderOwaspTop10Category,
+ renderOwaspTop10Category,
this.handleOwaspTop10ItemClick
);
}
renderCWEList() {
- return this.renderList('cweStats', 'cwe', this.renderCWECategory, this.handleCWEItemClick);
+ return this.renderList('cweStats', 'cwe', renderCWECategory, this.handleCWEItemClick);
}
renderCWESearch() {
const options = Object.keys(this.state.standards.cwe).map(cwe => ({
- label: this.renderCWECategory(cwe),
+ label: renderCWECategory(this.state.standards, cwe),
value: cwe
}));
return (
@@ -259,7 +232,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
return this.renderList(
'sansTop25Stats',
'sansTop25',
- this.renderSansTop25Category,
+ renderSansTop25Category,
this.handleSansTop25ItemClick
);
}
@@ -272,7 +245,9 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
name={translate('issues.facet.owaspTop10')}
onClick={this.handleOwaspTop10HeaderClick}
open={this.props.owaspTop10Open}
- values={this.props.owaspTop10.map(this.renderOwaspTop10Category)}
+ values={this.props.owaspTop10.map(item =>
+ renderOwaspTop10Category(this.state.standards, item)
+ )}
/>
{this.props.owaspTop10Open && this.renderOwaspTop10List()}
</FacetBox>
@@ -281,7 +256,9 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
name={translate('issues.facet.sansTop25')}
onClick={this.handleSansTop25HeaderClick}
open={this.props.sansTop25Open}
- values={this.props.sansTop25.map(this.renderSansTop25Category)}
+ values={this.props.sansTop25.map(item =>
+ renderSansTop25Category(this.state.standards, item)
+ )}
/>
{this.props.sansTop25Open && this.renderSansTop25List()}
</FacetBox>
@@ -290,7 +267,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
name={translate('issues.facet.cwe')}
onClick={this.handleCWEHeaderClick}
open={this.props.cweOpen}
- values={this.props.cwe.map(this.renderCWECategory)}
+ values={this.props.cwe.map(item => renderCWECategory(this.state.standards, item))}
/>
{this.props.cweOpen && this.renderCWEList()}
{this.props.cweOpen && this.renderCWESearch()}
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx
new file mode 100755
index 00000000000..90cce4b3fdc
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx
@@ -0,0 +1,158 @@
+/*
+ * 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';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import VulnerabilityList from './VulnerabilityList';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import { translate } from '../../../helpers/l10n';
+import { Component, BranchLike, SecurityHotspot } from '../../../app/types';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import Checkbox from '../../../components/controls/Checkbox';
+import { RawQuery } from '../../../helpers/query';
+import NotFound from '../../../app/components/NotFound';
+import '../style.css';
+import { getSecurityHotspots } from '../../../api/security-reports';
+import { isLongLivingBranch } from '../../../helpers/branches';
+import DocTooltip from '../../../components/docs/DocTooltip';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: Component;
+ location: { pathname: string; query: RawQuery };
+ params: { type: string };
+}
+
+interface State {
+ loading: boolean;
+ findings: Array<SecurityHotspot>;
+ hasVulnerabilities: boolean;
+ type: 'owaspTop10' | 'sansTop25' | 'cwe';
+ showCWE: boolean;
+}
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ static contextTypes = {
+ router: PropTypes.object.isRequired
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ loading: false,
+ findings: [],
+ hasVulnerabilities: false,
+ type: props.params.type === 'owasp_top_10' ? 'owaspTop10' : 'sansTop25',
+ showCWE: props.location.query.showCWE === 'true'
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchSecurityHotspots();
+ }
+
+ componentWillReceiveProps(newProps: Props) {
+ if (newProps.location.pathname !== this.props.location.pathname) {
+ const showCWE = newProps.location.query.showCWE === 'true';
+ const type = newProps.params.type === 'owasp_top_10' ? 'owaspTop10' : 'sansTop25';
+ this.setState({ type, showCWE }, this.fetchSecurityHotspots);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchSecurityHotspots = () => {
+ const { branchLike, component } = this.props;
+ this.setState({ loading: true });
+ getSecurityHotspots({
+ project: component.key,
+ standard: this.state.type,
+ includeDistribution: this.state.showCWE,
+ branch: isLongLivingBranch(branchLike) ? branchLike.name : undefined
+ })
+ .then(results => {
+ if (this.mounted) {
+ const hasVulnerabilities = results.categories.some(item => item.vulnerabilities > 0);
+ this.setState({ hasVulnerabilities, findings: results.categories, loading: false });
+ }
+ })
+ .catch(() => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ });
+ };
+
+ handleCheck = (checked: boolean) => {
+ const { router } = this.context;
+ router.push({
+ pathname: this.props.location.pathname,
+ query: { id: this.props.component.key, showCWE: checked }
+ });
+ this.setState({ showCWE: checked }, this.fetchSecurityHotspots);
+ };
+
+ render() {
+ const { branchLike, component, params } = this.props;
+ const { loading, findings, showCWE, type } = this.state;
+ if (params.type !== 'owasp_top_10' && params.type !== 'sans_top_25') {
+ return <NotFound withContainer={false} />;
+ }
+ return (
+ <div className="page page-limited" id="security-reports">
+ <Suggestions suggestions="security_reports" />
+ <Helmet title={translate('security_reports', type, 'page')} />
+ <header className="page-header">
+ <h1 className="page-title">{translate('security_reports', type, 'page')}</h1>
+ <div className="page-description">
+ {translate('security_reports', type, 'description')}
+ </div>
+ </header>
+ <div className="display-inline-flex-center">
+ <Checkbox
+ checked={showCWE}
+ className="spacer-left spacer-right vertical-middle"
+ disabled={!this.state.hasVulnerabilities}
+ id={'showCWE'}
+ onCheck={this.handleCheck}>
+ <label className="little-spacer-left" htmlFor={'showCWE'}>
+ {translate('security_reports.cwe.show')}
+ <DocTooltip className="spacer-left" doc="security-reports/cwe" />
+ </label>
+ </Checkbox>
+ </div>
+ <DeferredSpinner loading={loading}>
+ <VulnerabilityList
+ branchLike={branchLike}
+ component={component}
+ findings={findings}
+ showCWE={showCWE}
+ type={type}
+ />
+ </DeferredSpinner>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx
new file mode 100755
index 00000000000..c2bc6b51304
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx
@@ -0,0 +1,209 @@
+/*
+ * 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';
+import { Link } from 'react-router';
+import { translate } from '../../../helpers/l10n';
+import { SecurityHotspot, Component, BranchLike } from '../../../app/types';
+import Rating from '../../../components/ui/Rating';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import HelpTooltip from '../../../components/controls/HelpTooltip';
+import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
+import SecurityHotspotIcon from '../../../components/icons-components/SecurityHotspotIcon';
+import {
+ renderOwaspTop10Category,
+ renderSansTop25Category,
+ renderCWECategory,
+ Standards
+} from '../utils';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: Component;
+ findings: Array<SecurityHotspot>;
+ showCWE: boolean;
+ type: 'owaspTop10' | 'sansTop25' | 'cwe';
+}
+
+interface State {
+ standards: Standards;
+}
+
+export default class VulnerabilityList extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { standards: { owaspTop10: {}, sansTop25: {}, cwe: {} } };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadStandards();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadStandards = () => {
+ import('../../../helpers/standards.json')
+ .then(x => x.default)
+ .then(
+ ({ owaspTop10, sansTop25, cwe }: Standards) => {
+ if (this.mounted) {
+ this.setState({ standards: { owaspTop10, sansTop25, cwe } });
+ }
+ },
+ () => {}
+ );
+ };
+
+ getName(finding: SecurityHotspot, type: 'owaspTop10' | 'sansTop25' | 'cwe') {
+ const category = finding.category || finding.cwe || 'unknown';
+ const renderers = {
+ owaspTop10: renderOwaspTop10Category,
+ sansTop25: renderSansTop25Category,
+ cwe: renderCWECategory
+ };
+ return (
+ <>
+ {renderers[type](this.state.standards, category)}
+ {this.state.standards[type][category] &&
+ this.state.standards[type][category].description && (
+ <HelpTooltip
+ className="spacer-left"
+ overlay={this.state.standards[type][category].description}
+ />
+ )}
+ </>
+ );
+ }
+
+ renderFinding(finding: SecurityHotspot, isCWE?: boolean): React.ReactFragment {
+ const { branchLike, component, type } = this.props;
+ const params: { [name: string]: string | undefined } = {
+ ...getBranchLikeQuery(branchLike),
+ types: 'SECURITY_HOTSPOT'
+ };
+ params[type] = finding.category || finding.cwe;
+
+ const subFindings =
+ this.props.showCWE && finding.distribution
+ ? finding.distribution.map(f => this.renderFinding(f, true))
+ : null;
+
+ return (
+ <React.Fragment key={finding.category || finding.cwe}>
+ <tr>
+ {isCWE && <td />}
+ <td className="nowrap" colSpan={isCWE ? 1 : 2}>
+ <div className="display-inline-flex-center">
+ {this.getName(finding, isCWE ? 'cwe' : type)}
+ </div>
+ </td>
+ <td>
+ <div className="display-inline-flex-center">
+ <Link
+ to={getComponentIssuesUrl(component.key, { ...params, types: 'VULNERABILITY' })}>
+ {finding.vulnerabilities}
+ </Link>
+ <Link
+ className="link-no-underline spacer-left"
+ to={getComponentIssuesUrl(component.key, { ...params, types: 'VULNERABILITY' })}>
+ <Rating value={finding.vulnerabilityRating || 1} />
+ </Link>
+ </div>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ to={getComponentIssuesUrl(component.key, {
+ ...params,
+ types: 'SECURITY_HOTSPOT',
+ resolved: 'false',
+ statuses: 'OPEN'
+ })}>
+ {finding.openSecurityHotspots}
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ to={getComponentIssuesUrl(component.key, {
+ ...params,
+ types: 'SECURITY_HOTSPOT',
+ resolutions: 'FIXED'
+ })}>
+ {finding.toReviewSecurityHotspots}
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ to={getComponentIssuesUrl(component.key, {
+ ...params,
+ types: 'SECURITY_HOTSPOT',
+ resolutions: 'WONTFIX'
+ })}>
+ {finding.wontFixSecurityHotspots}
+ </Link>
+ </td>
+ </tr>
+ {subFindings}
+ </React.Fragment>
+ );
+ }
+
+ render() {
+ return (
+ <div className="boxed-group boxed-group-inner spacer-top">
+ <table className="data zebra">
+ <thead>
+ <tr>
+ <th className="security-category-column" colSpan={2}>
+ {translate('security_reports.list.categories')}
+ </th>
+ <th className="security-result-column">
+ <div className="display-inline-flex-center">
+ <VulnerabilityIcon className="spacer-right" />{' '}
+ {translate('security_reports.list.vulnerabilities')}
+ </div>
+ </th>
+ <th colSpan={3}>
+ <div className="display-inline-flex-center">
+ <SecurityHotspotIcon className="spacer-right" />{' '}
+ {translate('security_reports.list.hotspots')}
+ </div>
+ </th>
+ </tr>
+ <tr className="subheader">
+ <th colSpan={3} />
+ <th className="security-result-column">{translate('security_reports.line.open')}</th>
+ <th className="security-result-column">
+ {translate('security_reports.line.in_review')}
+ </th>
+ <th className="security-result-column">
+ {translate('security_reports.line.wont_fix')}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{this.props.findings.map(finding => this.renderFinding(finding))}</tbody>
+ </table>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..f2ed7089863
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+/* eslint-disable import/first, import/order */
+jest.mock('../../../../api/security-reports', () => ({
+ getSecurityHotspots: jest.fn(() => {
+ const distribution: any = [
+ {
+ cwe: '477',
+ vulnerabilities: 1,
+ vulnerabiliyRating: 1,
+ toReviewSecurityHotspots: 2,
+ openSecurityHotspots: 10,
+ wontFixSecurityHotspots: 0
+ },
+ {
+ cwe: '396',
+ vulnerabilities: 2,
+ vulnerabiliyRating: 2,
+ toReviewSecurityHotspots: 2,
+ openSecurityHotspots: 10,
+ wontFixSecurityHotspots: 0
+ }
+ ];
+ return Promise.resolve({
+ categories: [
+ {
+ category: 'a1',
+ vulnerabilities: 2,
+ vulnerabiliyRating: 5,
+ toReviewSecurityHotspots: 2,
+ openSecurityHotspots: 10,
+ wontFixSecurityHotspots: 0,
+ distribution
+ },
+ {
+ category: 'a2',
+ vulnerabilities: 3,
+ vulnerabiliyRating: 3,
+ toReviewSecurityHotspots: 8,
+ openSecurityHotspots: 100,
+ wontFixSecurityHotspots: 10
+ }
+ ]
+ });
+ })
+}));
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import { Component } from '../../../../app/types';
+import App from '../App';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+const getSecurityHotspots = require('../../../../api/security-reports')
+ .getSecurityHotspots as jest.Mock<any>;
+
+const component = { key: 'foo', name: 'Foo', qualifier: 'TRK' } as Component;
+const context = { router: { push: jest.fn() } };
+const location = { pathname: 'foo', query: {} };
+const locationWithCWE = { pathname: 'foo', query: { showCWE: 'true' } };
+const owaspParams = { type: 'owasp_top_10' };
+const sansParams = { type: 'sans_top_25' };
+const wrongParams = { type: 'foo' };
+
+beforeEach(() => {
+ getSecurityHotspots.mockClear();
+});
+
+it('renders error on wrong type parameters', () => {
+ const wrapper = shallow(<App component={component} location={location} params={wrongParams} />, {
+ context
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('renders owaspTop10', () => {
+ const wrapper = shallow(<App component={component} location={location} params={owaspParams} />, {
+ context
+ });
+ expect(getSecurityHotspots).toBeCalledWith({
+ project: 'foo',
+ standard: 'owaspTop10',
+ includeDistribution: false,
+ branch: undefined
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('renders with cwe', () => {
+ const wrapper = shallow(
+ <App component={component} location={locationWithCWE} params={owaspParams} />,
+ { context }
+ );
+ expect(getSecurityHotspots).toBeCalledWith({
+ project: 'foo',
+ standard: 'owaspTop10',
+ includeDistribution: true,
+ branch: undefined
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('handle checkbox for cwe display', async () => {
+ const wrapper = shallow(<App component={component} location={location} params={owaspParams} />, {
+ context
+ });
+ expect(getSecurityHotspots).toBeCalledWith({
+ project: 'foo',
+ standard: 'owaspTop10',
+ includeDistribution: false,
+ branch: undefined
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.find('Checkbox').prop<Function>('onCheck')(true);
+ await waitAndUpdate(wrapper);
+
+ expect(getSecurityHotspots).toBeCalledWith({
+ project: 'foo',
+ standard: 'owaspTop10',
+ includeDistribution: true,
+ branch: undefined
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('renders sansTop25', () => {
+ const wrapper = shallow(<App component={component} location={location} params={sansParams} />, {
+ context
+ });
+ expect(getSecurityHotspots).toBeCalledWith({
+ project: 'foo',
+ standard: 'sansTop25',
+ includeDistribution: false,
+ branch: undefined
+ });
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/VulnerabilityList-test.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/VulnerabilityList-test.tsx
new file mode 100644
index 00000000000..32f1efeaa98
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/VulnerabilityList-test.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import VulnerabilityList from '../VulnerabilityList';
+import { Component } from '../../../../app/types';
+
+jest.mock('../../../../helpers/standards.json', () => ({
+ default: {
+ owaspTop10: { a1: { title: 'a1 title' }, unknown: { title: 'Not OWAPS' } },
+ sansTop25: { 'risky-resource': { title: 'Risky Resource Management' } },
+ cwe: { 42: { title: 'cwe-42 title' }, unknown: { title: 'Unknown CWE' } }
+ }
+}));
+
+const component = { key: 'foo', name: 'Foo', qualifier: 'TRK' } as Component;
+const findings = [
+ {
+ category: 'a1',
+ vulnerabilities: 2,
+ vulnerabilityRating: 5,
+ toReviewSecurityHotspots: 2,
+ openSecurityHotspots: 10,
+ wontFixSecurityHotspots: 0,
+ distribution: [
+ {
+ cwe: '42',
+ vulnerabilities: 1,
+ vulnerabilityRating: 1,
+ toReviewSecurityHotspots: 2,
+ openSecurityHotspots: 10,
+ wontFixSecurityHotspots: 0
+ }
+ ]
+ },
+ {
+ category: 'unknown',
+ vulnerabilities: 3,
+ vulnerabilityRating: 3,
+ toReviewSecurityHotspots: 8,
+ openSecurityHotspots: 100,
+ wontFixSecurityHotspots: 10
+ }
+];
+
+it('renders', () => {
+ const wrapper = shallow(
+ <VulnerabilityList
+ component={component}
+ findings={findings}
+ showCWE={false}
+ type="owaspTop10"
+ />
+ );
+ expect(wrapper.find('tr').length).toBe(4);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('renders with cwe', () => {
+ const wrapper = shallow(
+ <VulnerabilityList component={component} findings={findings} showCWE={true} type="owaspTop10" />
+ );
+ expect(wrapper.find('tr').length).toBe(5);
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..58c69975ee9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,394 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`handle checkbox for cwe display 1`] = `
+<div
+ className="page page-limited"
+ id="security-reports"
+>
+ <Suggestions
+ suggestions="security_reports"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="security_reports.owaspTop10.page"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ security_reports.owaspTop10.page
+ </h1>
+ <div
+ className="page-description"
+ >
+ security_reports.owaspTop10.description
+ </div>
+ </header>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Checkbox
+ checked={false}
+ className="spacer-left spacer-right vertical-middle"
+ disabled={true}
+ id="showCWE"
+ onCheck={[Function]}
+ thirdState={false}
+ >
+ <label
+ className="little-spacer-left"
+ htmlFor="showCWE"
+ >
+ security_reports.cwe.show
+ <DocTooltip
+ className="spacer-left"
+ doc="security-reports/cwe"
+ />
+ </label>
+ </Checkbox>
+ </div>
+ <DeferredSpinner
+ loading={true}
+ timeout={100}
+ >
+ <VulnerabilityList
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ findings={Array []}
+ showCWE={false}
+ type="owaspTop10"
+ />
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`handle checkbox for cwe display 2`] = `
+<div
+ className="page page-limited"
+ id="security-reports"
+>
+ <Suggestions
+ suggestions="security_reports"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="security_reports.owaspTop10.page"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ security_reports.owaspTop10.page
+ </h1>
+ <div
+ className="page-description"
+ >
+ security_reports.owaspTop10.description
+ </div>
+ </header>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Checkbox
+ checked={true}
+ className="spacer-left spacer-right vertical-middle"
+ disabled={false}
+ id="showCWE"
+ onCheck={[Function]}
+ thirdState={false}
+ >
+ <label
+ className="little-spacer-left"
+ htmlFor="showCWE"
+ >
+ security_reports.cwe.show
+ <DocTooltip
+ className="spacer-left"
+ doc="security-reports/cwe"
+ />
+ </label>
+ </Checkbox>
+ </div>
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ >
+ <VulnerabilityList
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ findings={
+ Array [
+ Object {
+ "category": "a1",
+ "distribution": Array [
+ Object {
+ "cwe": "477",
+ "openSecurityHotspots": 10,
+ "toReviewSecurityHotspots": 2,
+ "vulnerabilities": 1,
+ "vulnerabiliyRating": 1,
+ "wontFixSecurityHotspots": 0,
+ },
+ Object {
+ "cwe": "396",
+ "openSecurityHotspots": 10,
+ "toReviewSecurityHotspots": 2,
+ "vulnerabilities": 2,
+ "vulnerabiliyRating": 2,
+ "wontFixSecurityHotspots": 0,
+ },
+ ],
+ "openSecurityHotspots": 10,
+ "toReviewSecurityHotspots": 2,
+ "vulnerabilities": 2,
+ "vulnerabiliyRating": 5,
+ "wontFixSecurityHotspots": 0,
+ },
+ Object {
+ "category": "a2",
+ "openSecurityHotspots": 100,
+ "toReviewSecurityHotspots": 8,
+ "vulnerabilities": 3,
+ "vulnerabiliyRating": 3,
+ "wontFixSecurityHotspots": 10,
+ },
+ ]
+ }
+ showCWE={true}
+ type="owaspTop10"
+ />
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`renders error on wrong type parameters 1`] = `
+<NotFound
+ withContainer={false}
+/>
+`;
+
+exports[`renders owaspTop10 1`] = `
+<div
+ className="page page-limited"
+ id="security-reports"
+>
+ <Suggestions
+ suggestions="security_reports"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="security_reports.owaspTop10.page"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ security_reports.owaspTop10.page
+ </h1>
+ <div
+ className="page-description"
+ >
+ security_reports.owaspTop10.description
+ </div>
+ </header>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Checkbox
+ checked={false}
+ className="spacer-left spacer-right vertical-middle"
+ disabled={true}
+ id="showCWE"
+ onCheck={[Function]}
+ thirdState={false}
+ >
+ <label
+ className="little-spacer-left"
+ htmlFor="showCWE"
+ >
+ security_reports.cwe.show
+ <DocTooltip
+ className="spacer-left"
+ doc="security-reports/cwe"
+ />
+ </label>
+ </Checkbox>
+ </div>
+ <DeferredSpinner
+ loading={true}
+ timeout={100}
+ >
+ <VulnerabilityList
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ findings={Array []}
+ showCWE={false}
+ type="owaspTop10"
+ />
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`renders sansTop25 1`] = `
+<div
+ className="page page-limited"
+ id="security-reports"
+>
+ <Suggestions
+ suggestions="security_reports"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="security_reports.sansTop25.page"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ security_reports.sansTop25.page
+ </h1>
+ <div
+ className="page-description"
+ >
+ security_reports.sansTop25.description
+ </div>
+ </header>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Checkbox
+ checked={false}
+ className="spacer-left spacer-right vertical-middle"
+ disabled={true}
+ id="showCWE"
+ onCheck={[Function]}
+ thirdState={false}
+ >
+ <label
+ className="little-spacer-left"
+ htmlFor="showCWE"
+ >
+ security_reports.cwe.show
+ <DocTooltip
+ className="spacer-left"
+ doc="security-reports/cwe"
+ />
+ </label>
+ </Checkbox>
+ </div>
+ <DeferredSpinner
+ loading={true}
+ timeout={100}
+ >
+ <VulnerabilityList
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ findings={Array []}
+ showCWE={false}
+ type="sansTop25"
+ />
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`renders with cwe 1`] = `
+<div
+ className="page page-limited"
+ id="security-reports"
+>
+ <Suggestions
+ suggestions="security_reports"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="security_reports.owaspTop10.page"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ security_reports.owaspTop10.page
+ </h1>
+ <div
+ className="page-description"
+ >
+ security_reports.owaspTop10.description
+ </div>
+ </header>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Checkbox
+ checked={true}
+ className="spacer-left spacer-right vertical-middle"
+ disabled={true}
+ id="showCWE"
+ onCheck={[Function]}
+ thirdState={false}
+ >
+ <label
+ className="little-spacer-left"
+ htmlFor="showCWE"
+ >
+ security_reports.cwe.show
+ <DocTooltip
+ className="spacer-left"
+ doc="security-reports/cwe"
+ />
+ </label>
+ </Checkbox>
+ </div>
+ <DeferredSpinner
+ loading={true}
+ timeout={100}
+ >
+ <VulnerabilityList
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ findings={Array []}
+ showCWE={true}
+ type="owaspTop10"
+ />
+ </DeferredSpinner>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/VulnerabilityList-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/VulnerabilityList-test.tsx.snap
new file mode 100644
index 00000000000..437b86a78db
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/VulnerabilityList-test.tsx.snap
@@ -0,0 +1,744 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="boxed-group boxed-group-inner spacer-top"
+>
+ <table
+ className="data zebra"
+ >
+ <thead>
+ <tr>
+ <th
+ className="security-category-column"
+ colSpan={2}
+ >
+ security_reports.list.categories
+ </th>
+ <th
+ className="security-result-column"
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <VulnerabilityIcon
+ className="spacer-right"
+ />
+
+ security_reports.list.vulnerabilities
+ </div>
+ </th>
+ <th
+ colSpan={3}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <SecurityHotspotIcon
+ className="spacer-right"
+ />
+
+ security_reports.list.hotspots
+ </div>
+ </th>
+ </tr>
+ <tr
+ className="subheader"
+ >
+ <th
+ colSpan={3}
+ />
+ <th
+ className="security-result-column"
+ >
+ security_reports.line.open
+ </th>
+ <th
+ className="security-result-column"
+ >
+ security_reports.line.in_review
+ </th>
+ <th
+ className="security-result-column"
+ >
+ security_reports.line.wont_fix
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <React.Fragment
+ key="a1"
+ >
+ <tr>
+ <td
+ className="nowrap"
+ colSpan={2}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <React.Fragment>
+ A1
+ </React.Fragment>
+ </div>
+ </td>
+ <td>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ 2
+ </Link>
+ <Link
+ className="link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ <Rating
+ value={5}
+ />
+ </Link>
+ </div>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "resolved": "false",
+ "statuses": "OPEN",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 10
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "resolutions": "FIXED",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 2
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "resolutions": "WONTFIX",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 0
+ </Link>
+ </td>
+ </tr>
+ </React.Fragment>
+ <React.Fragment
+ key="unknown"
+ >
+ <tr>
+ <td
+ className="nowrap"
+ colSpan={2}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <React.Fragment>
+ UNKNOWN
+ </React.Fragment>
+ </div>
+ </td>
+ <td>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ 3
+ </Link>
+ <Link
+ className="link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ <Rating
+ value={3}
+ />
+ </Link>
+ </div>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "resolved": "false",
+ "statuses": "OPEN",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 100
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "resolutions": "FIXED",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 8
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "resolutions": "WONTFIX",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 10
+ </Link>
+ </td>
+ </tr>
+ </React.Fragment>
+ </tbody>
+ </table>
+</div>
+`;
+
+exports[`renders with cwe 1`] = `
+<div
+ className="boxed-group boxed-group-inner spacer-top"
+>
+ <table
+ className="data zebra"
+ >
+ <thead>
+ <tr>
+ <th
+ className="security-category-column"
+ colSpan={2}
+ >
+ security_reports.list.categories
+ </th>
+ <th
+ className="security-result-column"
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <VulnerabilityIcon
+ className="spacer-right"
+ />
+
+ security_reports.list.vulnerabilities
+ </div>
+ </th>
+ <th
+ colSpan={3}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <SecurityHotspotIcon
+ className="spacer-right"
+ />
+
+ security_reports.list.hotspots
+ </div>
+ </th>
+ </tr>
+ <tr
+ className="subheader"
+ >
+ <th
+ colSpan={3}
+ />
+ <th
+ className="security-result-column"
+ >
+ security_reports.line.open
+ </th>
+ <th
+ className="security-result-column"
+ >
+ security_reports.line.in_review
+ </th>
+ <th
+ className="security-result-column"
+ >
+ security_reports.line.wont_fix
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <React.Fragment
+ key="a1"
+ >
+ <tr>
+ <td
+ className="nowrap"
+ colSpan={2}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <React.Fragment>
+ A1
+ </React.Fragment>
+ </div>
+ </td>
+ <td>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ 2
+ </Link>
+ <Link
+ className="link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ <Rating
+ value={5}
+ />
+ </Link>
+ </div>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "resolved": "false",
+ "statuses": "OPEN",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 10
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "resolutions": "FIXED",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 2
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "a1",
+ "resolutions": "WONTFIX",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 0
+ </Link>
+ </td>
+ </tr>
+ <React.Fragment
+ key="42"
+ >
+ <tr>
+ <td />
+ <td
+ className="nowrap"
+ colSpan={1}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <React.Fragment>
+ CWE-42
+ </React.Fragment>
+ </div>
+ </td>
+ <td>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "42",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ 1
+ </Link>
+ <Link
+ className="link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "42",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ <Rating
+ value={1}
+ />
+ </Link>
+ </div>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "42",
+ "resolved": "false",
+ "statuses": "OPEN",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 10
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "42",
+ "resolutions": "FIXED",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 2
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "42",
+ "resolutions": "WONTFIX",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 0
+ </Link>
+ </td>
+ </tr>
+ </React.Fragment>
+ </React.Fragment>
+ <React.Fragment
+ key="unknown"
+ >
+ <tr>
+ <td
+ className="nowrap"
+ colSpan={2}
+ >
+ <div
+ className="display-inline-flex-center"
+ >
+ <React.Fragment>
+ UNKNOWN
+ </React.Fragment>
+ </div>
+ </td>
+ <td>
+ <div
+ className="display-inline-flex-center"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ 3
+ </Link>
+ <Link
+ className="link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ <Rating
+ value={3}
+ />
+ </Link>
+ </div>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "resolved": "false",
+ "statuses": "OPEN",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 100
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "resolutions": "FIXED",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 8
+ </Link>
+ </td>
+ <td>
+ <Link
+ className="spacer-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "foo",
+ "owaspTop10": "unknown",
+ "resolutions": "WONTFIX",
+ "types": "SECURITY_HOTSPOT",
+ },
+ }
+ }
+ >
+ 10
+ </Link>
+ </td>
+ </tr>
+ </React.Fragment>
+ </tbody>
+ </table>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/securityReports/style.css b/server/sonar-web/src/main/js/apps/securityReports/style.css
new file mode 100644
index 00000000000..bf6e1ab8ace
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/style.css
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+.security-category-column {
+ width: 52%;
+}
+
+.security-result-column {
+ width: 12%;
+}
diff --git a/server/sonar-web/src/main/js/apps/securityReports/utils.ts b/server/sonar-web/src/main/js/apps/securityReports/utils.ts
new file mode 100755
index 00000000000..02de49d1c29
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/securityReports/utils.ts
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+export interface Standards {
+ owaspTop10: { [x: string]: { title: string; description?: string } };
+ sansTop25: { [x: string]: { title: string; description?: string } };
+ cwe: { [x: string]: { title: string; description?: string } };
+}
+
+export function renderOwaspTop10Category(standards: Standards, category: string): string {
+ const record = standards.owaspTop10[category];
+ if (!record) {
+ return category.toUpperCase();
+ } else if (category === 'unknown') {
+ return record.title;
+ } else {
+ return `${category.toUpperCase()} - ${record.title}`;
+ }
+}
+
+export function renderCWECategory(standards: Standards, category: string): string {
+ const record = standards.cwe[category];
+ if (!record) {
+ return `CWE-${category}`;
+ } else if (category === 'unknown') {
+ return record.title;
+ } else {
+ return `CWE-${category} - ${record.title}`;
+ }
+}
+
+export function renderSansTop25Category(standards: Standards, category: string): string {
+ const record = standards.sansTop25[category];
+ return record ? record.title : category;
+}
diff --git a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx
index f4602d3b7ab..acfccd40c9a 100644
--- a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx
@@ -56,7 +56,7 @@ export default class Checkbox extends React.PureComponent<Props> {
return (
<a
className={classNames('link-checkbox', this.props.className, {
- 'text-muted': this.props.disabled,
+ note: this.props.disabled,
disabled: this.props.disabled
})}
href="#"
diff --git a/server/sonar-web/src/main/js/helpers/standards.json b/server/sonar-web/src/main/js/helpers/standards.json
index fca50b88517..cea1023329c 100644
--- a/server/sonar-web/src/main/js/helpers/standards.json
+++ b/server/sonar-web/src/main/js/helpers/standards.json
@@ -3,12 +3,12 @@
"a1": {
"title": "Injection",
"description":
- "Injection flaws, such as SQL, NoSQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization."
+ "Injection flaws, such as SQL, NoSQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization."
},
"a2": {
"title": "Broken Authentication",
"description":
- "Application functions related to authentication and session management are often implemented incorrectly, allowing attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users’ identities temporarily or permanently."
+ "Application functions related to authentication and session management are often implemented incorrectly, allowing attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users’ identities temporarily or permanently."
},
"a3": {
"title": "Sensitive Data Exposure",
@@ -23,7 +23,7 @@
"a5": {
"title": "Broken Access Control",
"description":
- "Restrictions on what authenticated users are allowed to do are often not properly enforced. Attackers can exploit these flaws to access unauthorized functionality and/or data, such as access other users' accounts, view sensitive files, modify other users’ data, change access rights, etc."
+ "Restrictions on what authenticated users are allowed to do are often not properly enforced. Attackers can exploit these flaws to access unauthorized functionality and/or data, such as access other users' accounts, view sensitive files, modify other users’ data, change access rights, etc."
},
"a6": {
"title": "Security Misconfiguration",
@@ -33,7 +33,7 @@
"a7": {
"title": "Cross-Site Scripting (XSS)",
"description":
- "XSS flaws occur whenever an application includes untrusted data in a new web page without proper validation or escaping, or updates an existing web page with user-supplied data using a browser API that can create HTML or JavaScript. XSS allows attackers to execute scripts in the victim’s browser which can hijack user sessions, deface web sites, or redirect the user to malicious sites."
+ "XSS flaws occur whenever an application includes untrusted data in a new web page without proper validation or escaping, or updates an existing web page with user-supplied data using a browser API that can create HTML or JavaScript. XSS allows attackers to execute scripts in the victim’s browser which can hijack user sessions, deface web sites, or redirect the user to malicious sites."
},
"a8": {
"title": "Insecure Deserialization",
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 3e8c5ae3c6c..bcb0b5288d0 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -442,6 +442,7 @@ layout.login=Log in
layout.logout=Log out
layout.measures=Measures
layout.settings=Administration
+layout.security_reports=Security Reports
layout.sonar.slogan=Continuous Code Quality
sidebar.projects=Projects
@@ -2012,7 +2013,22 @@ organizations_permissions.scan.desc=Ability to get all settings required to perf
organizations_permissions.provisioning=Create Projects
organizations_permissions.provisioning.desc=Ability to initialize a project so its settings can be configured before the first analysis.
-
+#------------------------------------------------------------------------------
+#
+# SECURITY REPORTS PAGE
+#
+#------------------------------------------------------------------------------
+security_reports.owaspTop10.page=OWASP Top 10
+security_reports.sansTop25.page=SANS Top 25
+security_reports.owaspTop10.description=Track Vulnerabilities and Security Hotspots conforming to OWASP Top 10 standard.
+security_reports.sansTop25.description=Track Vulnerabilities and Security Hotspots conforming to SANS Top 25 standard.
+security_reports.list.categories=Categories
+security_reports.list.vulnerabilities=Vulnerabilities
+security_reports.list.hotspots=Security Hotspots
+security_reports.line.open=Open
+security_reports.line.wont_fix=Won't Fix
+security_reports.line.in_review=In Review
+security_reports.cwe.show=Show CWE distribution
#------------------------------------------------------------------------------
#