]> source.dussan.org Git - sonarqube.git/commitdiff
Add security hotspots page (#478)
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Tue, 10 Jul 2018 11:12:50 +0000 (13:12 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 17 Jul 2018 18:21:25 +0000 (20:21 +0200)
19 files changed:
server/sonar-docs/src/tooltips/security-reports/cwe.md [new file with mode: 0644]
server/sonar-web/src/main/js/api/security-reports.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
server/sonar-web/src/main/js/app/styles/init/tables.css
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx
server/sonar-web/src/main/js/apps/securityReports/components/App.tsx [new file with mode: 0755]
server/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx [new file with mode: 0755]
server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/securityReports/components/__tests__/VulnerabilityList-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/VulnerabilityList-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/securityReports/style.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/securityReports/utils.ts [new file with mode: 0755]
server/sonar-web/src/main/js/components/controls/Checkbox.tsx
server/sonar-web/src/main/js/helpers/standards.json
sonar-core/src/main/resources/org/sonar/l10n/core.properties

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 (file)
index 0000000..313e023
--- /dev/null
@@ -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 (file)
index 0000000..2747ea7
--- /dev/null
@@ -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);
+}
index 53009cc4d40fd4aa0634f7de7a36dcc2cde40e02..ca3a9ce6f5f6006b5f24c92f62ae9b4f2f8f06e3 100644 (file)
@@ -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()}
index a62c3ce9e0ead04ec1df6587fb1d9659b0ed721e..40cbe0f76bb1bb43b55101f72952271c6d73cb81 100644 (file)
@@ -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"
index 2a2a29366bb255838266a170fde1079948a6913b..880cfc80bb1d9811e71b126893761782c4bb58df 100644 (file)
@@ -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;
 }
index b546c8a212fa92557bca589f6cce0c9471afba44..741521d83963e837d1077ae210c68731298e96e0 100644 (file)
@@ -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;
index 97e9845a34485186a612b1a9467d7991173e5470..69602ff1e134bf7f2107989c6caa93a15d104b11 100644 (file)
@@ -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"
index 33d6d0e6f6690dd8ae945307e4a8b00ba13ab060..f305748e4791885136e733eb683b925045403396 100644 (file)
@@ -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 (executable)
index 0000000..90cce4b
--- /dev/null
@@ -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 (executable)
index 0000000..c2bc6b5
--- /dev/null
@@ -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 (file)
index 0000000..f2ed708
--- /dev/null
@@ -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 (file)
index 0000000..32f1efe
--- /dev/null
@@ -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 (file)
index 0000000..58c6997
--- /dev/null
@@ -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 (file)
index 0000000..437b86a
--- /dev/null
@@ -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 (file)
index 0000000..bf6e1ab
--- /dev/null
@@ -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 (executable)
index 0000000..02de49d
--- /dev/null
@@ -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;
+}
index f4602d3b7ab5f92bf5e991ff7b24b41a0d313b4c..acfccd40c9a7bfc4207988e36c6bdc8eb6af85ad 100644 (file)
@@ -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="#"
index fca50b885175b0e9a5fa1d04cffa10d78e87d7a7..cea1023329c64000d153f3b9c259f212e57b88dc 100644 (file)
@@ -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 attackers 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 victims 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",
index 3e8c5ae3c6cda526e8a7d5108412873331feda1e..bcb0b5288d06dcaf0803bc6a7185b2f0599f9979 100644 (file)
@@ -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
 
 #------------------------------------------------------------------------------
 #