diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2019-04-17 15:33:26 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-05-07 09:54:27 +0200 |
commit | 378fa0ac1a561bcd9d0c5f2e063a9c4ea17a0511 (patch) | |
tree | 608eac38ca66321dd85f5101d2964008306964a0 /server/sonar-web/src/main/js | |
parent | 699a47b373815a9cf368e287b343d151f5fc0a59 (diff) | |
download | sonarqube-378fa0ac1a561bcd9d0c5f2e063a9c4ea17a0511.tar.gz sonarqube-378fa0ac1a561bcd9d0c5f2e063a9c4ea17a0511.zip |
SONAR-11983 Create a new 'SonarSource' security report page
Diffstat (limited to 'server/sonar-web/src/main/js')
8 files changed, 381 insertions, 26 deletions
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 1c59dad2338..4f779bf27ac 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 @@ -160,6 +160,16 @@ export class ComponentNavMenu extends React.PureComponent<Props> { <li> <Link activeClassName="active" + to={{ + pathname: '/project/security_reports/sonarsource_security', + query: this.getQuery() + }}> + {translate('security_reports.sonarsourceSecurity.page')} + </Link> + </li> + <li> + <Link + activeClassName="active" to={{ pathname: '/project/security_reports/owasp_top_10', query: this.getQuery() }}> {translate('security_reports.owaspTop10.page')} </Link> 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 221e7dd6f26..34ca59e182e 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 @@ -50,6 +50,23 @@ exports[`should work for all qualifiers 1`] = ` style={Object {}} to={ Object { + "pathname": "/project/security_reports/sonarsource_security", + "query": Object { + "id": "foo", + }, + } + } + > + security_reports.sonarsourceSecurity.page + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { "pathname": "/project/security_reports/owasp_top_10", "query": Object { "id": "foo", @@ -267,6 +284,23 @@ exports[`should work for all qualifiers 2`] = ` style={Object {}} to={ Object { + "pathname": "/project/security_reports/sonarsource_security", + "query": Object { + "id": "foo", + }, + } + } + > + security_reports.sonarsourceSecurity.page + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { "pathname": "/project/security_reports/owasp_top_10", "query": Object { "id": "foo", @@ -433,6 +467,23 @@ exports[`should work for all qualifiers 3`] = ` style={Object {}} to={ Object { + "pathname": "/project/security_reports/sonarsource_security", + "query": Object { + "id": "foo", + }, + } + } + > + security_reports.sonarsourceSecurity.page + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { "pathname": "/project/security_reports/owasp_top_10", "query": Object { "id": "foo", @@ -570,6 +621,23 @@ exports[`should work for all qualifiers 4`] = ` style={Object {}} to={ Object { + "pathname": "/project/security_reports/sonarsource_security", + "query": Object { + "id": "foo", + }, + } + } + > + security_reports.sonarsourceSecurity.page + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { "pathname": "/project/security_reports/owasp_top_10", "query": Object { "id": "foo", @@ -738,6 +806,24 @@ exports[`should work for long-living branches 1`] = ` style={Object {}} to={ Object { + "pathname": "/project/security_reports/sonarsource_security", + "query": Object { + "branch": "release", + "id": "foo", + }, + } + } + > + security_reports.sonarsourceSecurity.page + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { "pathname": "/project/security_reports/owasp_top_10", "query": Object { "branch": "release", @@ -882,6 +968,24 @@ exports[`should work for long-living branches 2`] = ` style={Object {}} to={ Object { + "pathname": "/project/security_reports/sonarsource_security", + "query": Object { + "branch": "release", + "id": "foo", + }, + } + } + > + security_reports.sonarsourceSecurity.page + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { "pathname": "/project/security_reports/owasp_top_10", "query": Object { "branch": "release", 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 index 6dc80000947..37c049a0c86 100755 --- a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx @@ -21,17 +21,18 @@ import * as React from 'react'; import Helmet from 'react-helmet'; import { Link } from 'react-router'; import VulnerabilityList from './VulnerabilityList'; -import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; -import { translate } from '../../../helpers/l10n'; import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import { Alert } from '../../../components/ui/Alert'; import Checkbox from '../../../components/controls/Checkbox'; -import NotFound from '../../../app/components/NotFound'; -import { getSecurityHotspots } from '../../../api/security-reports'; -import { isLongLivingBranch } from '../../../helpers/branches'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; import DocTooltip from '../../../components/docs/DocTooltip'; -import { Alert } from '../../../components/ui/Alert'; +import NotFound from '../../../app/components/NotFound'; +import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import { withRouter, Location, Router } from '../../../components/hoc/withRouter'; +import { translate } from '../../../helpers/l10n'; +import { isLongLivingBranch } from '../../../helpers/branches'; +import { getSecurityHotspots } from '../../../api/security-reports'; +import { getType } from '../utils'; import '../style.css'; interface Props { @@ -59,7 +60,7 @@ export class App extends React.PureComponent<Props, State> { loading: false, findings: [], hasVulnerabilities: false, - type: this.getType(props.params.type), + type: getType(props.params.type), showCWE: props.location.query.showCWE === 'true' }; } @@ -72,7 +73,7 @@ export class App extends React.PureComponent<Props, State> { componentWillReceiveProps(newProps: Props) { if (newProps.location.pathname !== this.props.location.pathname) { const showCWE = newProps.location.query.showCWE === 'true'; - const type = this.getType(newProps.params.type); + const type = getType(newProps.params.type); this.setState({ type, showCWE }, this.fetchSecurityHotspots); } } @@ -81,16 +82,6 @@ export class App extends React.PureComponent<Props, State> { this.mounted = false; } - getType = (type: string): T.StandardType => { - if (type === 'owasp_top_10') { - return 'owaspTop10'; - } else if (type === 'sans_top_25') { - return 'sansTop25'; - } else { - return 'sonarsource'; - } - }; - fetchSecurityHotspots = () => { const { branchLike, component } = this.props; this.setState({ loading: true }); @@ -150,7 +141,7 @@ export class App extends React.PureComponent<Props, State> { 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') { + if (!['owasp_top_10', 'sans_top_25', 'sonarsource_security'].includes(params.type)) { return <NotFound withContainer={false} />; } return ( 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 index a508d30c120..4d1d4dc6e86 100755 --- a/server/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx +++ b/server/sonar-web/src/main/js/apps/securityReports/components/VulnerabilityList.tsx @@ -27,7 +27,12 @@ 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 } from '../utils'; +import { + renderOwaspTop10Category, + renderSansTop25Category, + renderCWECategory, + renderSonarSourceSecurityCategory +} from '../utils'; import DetachIcon from '../../../components/icons-components/DetachIcon'; import Tooltip from '../../../components/controls/Tooltip'; import { getRatingTooltip } from '../../../helpers/measures'; @@ -86,9 +91,7 @@ export default class VulnerabilityList extends React.PureComponent<Props, State> owaspTop10: renderOwaspTop10Category, sansTop25: renderSansTop25Category, cwe: renderCWECategory, - sonarsourceSecurity: () => { - /* TODO */ - } + sonarsourceSecurity: renderSonarSourceSecurityCategory }; return ( <> 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 index 4f43520f752..12bf3c5b428 100644 --- 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 @@ -89,6 +89,7 @@ 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 sonarParams = { type: 'sonarsource_security' }; const wrongParams = { type: 'foo' }; beforeEach(() => { @@ -190,3 +191,22 @@ it('renders sansTop25', () => { }); expect(wrapper).toMatchSnapshot(); }); + +it('renders sonarsourceSecurity', async () => { + const wrapper = shallow( + <App + component={component} + location={location} + params={sonarParams} + router={{ push: jest.fn() }} + /> + ); + await waitAndUpdate(wrapper); + expect(getSecurityHotspots).toBeCalledWith({ + project: 'foo', + standard: 'sonarsourceSecurity', + includeDistribution: false, + branch: undefined + }); + 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 index 2884f4f0c57..b89aa9c57c4 100644 --- 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 @@ -466,6 +466,150 @@ exports[`renders sansTop25 1`] = ` </div> `; +exports[`renders sonarsourceSecurity 1`] = ` +<div + className="page page-limited" + id="security-reports" +> + <Suggestions + suggestions="security_reports" + /> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="security_reports.sonarsourceSecurity.page" + /> + <header + className="page-header" + > + <A11ySkipTarget + anchor="security_main" + /> + <h1 + className="page-title" + > + security_reports.sonarsourceSecurity.page + </h1> + <div + className="page-description" + > + security_reports.sonarsourceSecurity.description + <Link + className="spacer-left" + onlyActiveOnIndex={false} + style={Object {}} + target="_blank" + to={ + Object { + "pathname": "/documentation/user-guide/security-reports/", + } + } + > + learn_more + </Link> + <Alert + className="spacer-top" + display="inline" + variant="info" + > + security_reports.more_rules + </Alert> + </div> + </header> + <div + className="display-inline-flex-center" + > + <Checkbox + checked={false} + 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={Promise {}} + /> + </label> + </Checkbox> + </div> + <DeferredSpinner + loading={false} + timeout={100} + > + <VulnerabilityList + component={ + Object { + "key": "foo", + "name": "Foo", + "qualifier": "TRK", + } + } + findings={ + Array [ + Object { + "activeRules": 1, + "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, + "totalRules": 1, + "vulnerabilities": 2, + "vulnerabiliyRating": 5, + "wontFixSecurityHotspots": 0, + }, + Object { + "activeRules": 1, + "category": "a2", + "openSecurityHotspots": 100, + "toReviewSecurityHotspots": 8, + "totalRules": 1, + "vulnerabilities": 3, + "vulnerabiliyRating": 3, + "wontFixSecurityHotspots": 10, + }, + Object { + "activeRules": 0, + "category": "a3", + "openSecurityHotspots": 100, + "toReviewSecurityHotspots": 8, + "totalRules": 1, + "vulnerabilities": 3, + "vulnerabiliyRating": 3, + "wontFixSecurityHotspots": 10, + }, + ] + } + showCWE={false} + type="sonarsourceSecurity" + /> + </DeferredSpinner> +</div> +`; + exports[`renders with cwe 1`] = ` <div className="page page-limited" diff --git a/server/sonar-web/src/main/js/apps/securityReports/utils.ts b/server/sonar-web/src/main/js/apps/securityReports/utils.ts index e53518ab2f4..604133f285e 100755 --- a/server/sonar-web/src/main/js/apps/securityReports/utils.ts +++ b/server/sonar-web/src/main/js/apps/securityReports/utils.ts @@ -52,6 +52,25 @@ export function renderSansTop25Category( return addPrefix(record ? record.title : category, 'SANS', withPrefix); } +export function renderSonarSourceCategory( + standards: T.Standards, + category: string, + withPrefix = false +): string { + const record = standards.sonarsourceSecurity[category]; + return addPrefix(record ? record.title : category, 'SONAR', withPrefix); +} + function addPrefix(title: string, prefix: string, withPrefix: boolean) { return withPrefix ? `${prefix} ${title}` : title; } + +const TYPES_MAP: T.Dict<T.StandardType> = { + owasp_top_10: 'owaspTop10', + sans_top_25: 'sansTop25', + sonarsource_security: 'sonarsourceSecurity' +}; + +export function getType(type: string): T.StandardType { + return TYPES_MAP[type] || 'sonarsourceSecurity'; +} diff --git a/server/sonar-web/src/main/js/helpers/standards.json b/server/sonar-web/src/main/js/helpers/standards.json index f2d32e1715b..051fc01038d 100644 --- a/server/sonar-web/src/main/js/helpers/standards.json +++ b/server/sonar-web/src/main/js/helpers/standards.json @@ -3619,8 +3619,72 @@ }, "99": { "title": "Improper Control of Resource Identifiers ('Resource Injection')", - "description": - "The software receives input from an upstream component, but it does not restrict or incorrectly restricts the input before it is used as an identifier for a resource that may be outside the intended sphere of control." + "description": "The software receives input from an upstream component, but it does not restrict or incorrectly restricts the input before it is used as an identifier for a resource that may be outside the intended sphere of control." + } + }, + "sonarsourceSecurity": { + "sql-injection": { + "title": "SQL Injection" + }, + "rce": { + "title": "Code Injection (RCE)" + }, + "object-injection": { + "title": "Object Injection" + }, + "command-injection": { + "title": "Command Injection" + }, + "path-traversal-injection": { + "title": "Path Traversal Injection" + }, + "ldap-injection": { + "title": "LDAP Injection" + }, + "xpath-injection": { + "title": "XPath Injection" + }, + "expression-lang-injection": { + "title": "Expression Language Injection" + }, + "log-injection": { + "title": "Log Injection" + }, + "xxe": { + "title": "XML External Entity (XXE)" + }, + "xss": { + "title": "Cross-Site Scripting (XSS)" + }, + "dos": { + "title": "Denial of Service (DoS)" + }, + "ssrf": { + "title": "Server-Side Request Forgery (SSRF)" + }, + "csrf": { + "title": "Cross-Site Request Forgery (CSRF)" + }, + "http-response-splitting": { + "title": "HTTP Response Splitting" + }, + "open-redirect": { + "title": "Open Redirect" + }, + "weak-cryptography": { + "title": "Weak Cryptography" + }, + "auth": { + "title": "Authentication" + }, + "insecure-conf": { + "title": "Insecure Configuration" + }, + "file-manipulation": { + "title": "File Manipulation" + }, + "others": { + "title": "Others" } } } |