--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { cloneDeep } from 'lodash';
+import { RequestData } from '../../helpers/request';
+import { getStandards } from '../../helpers/security-standard';
+import { mockPaging } from '../../helpers/testMocks';
+import { RawFacet, RawIssuesResponse, ReferencedComponent } from '../../types/issues';
+import { Standards } from '../../types/security';
+import { searchIssues } from '../issues';
+
+function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
+ return {
+ key: 'component1',
+ name: 'Component1',
+ uuid: 'id1',
+ ...override
+ };
+}
+
+export default class IssueServiceMock {
+ isAdmin = false;
+ standards?: Standards;
+
+ constructor() {
+ (searchIssues as jest.Mock).mockImplementation(this.listHandler);
+ }
+
+ reset() {
+ this.setIsAdmin(false);
+ }
+
+ async getStandards(): Promise<Standards> {
+ if (this.standards) {
+ return this.standards;
+ }
+ this.standards = await getStandards();
+ return this.standards;
+ }
+
+ owasp2021FacetList(): RawFacet {
+ return {
+ property: 'owaspTop10-2021',
+ values: [{ val: 'a1', count: 0 }]
+ };
+ }
+
+ setIsAdmin(isAdmin: boolean) {
+ this.isAdmin = isAdmin;
+ }
+
+ listHandler = (query: RequestData): Promise<RawIssuesResponse> => {
+ const facets = query.facets.split(',').map((name: string) => {
+ if (name === 'owaspTop10-2021') {
+ return this.owasp2021FacetList();
+ }
+ return {
+ property: name,
+ values: []
+ };
+ });
+ return this.reply({
+ components: [mockReferenceComponent()],
+ effortTotal: 199629,
+ facets,
+ issues: [],
+ languages: [],
+ paging: mockPaging()
+ });
+ };
+
+ reply<T>(response: T): Promise<T> {
+ return Promise.resolve(cloneDeep(response));
+ }
+}
openFacets: {
languages: true,
owaspTop10: shouldOpenStandardsChildFacet({}, query, SecurityStandard.OWASP_TOP10),
+ 'owaspTop10-2021': shouldOpenStandardsChildFacet(
+ {},
+ query,
+ SecurityStandard.OWASP_TOP10_2021
+ ),
sansTop25: shouldOpenStandardsChildFacet({}, query, SecurityStandard.SANS_TOP25),
sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query),
standards: shouldOpenStandardsFacet({}, query),
cweStats={props.facets && props.facets.cwe}
fetchingCwe={false}
fetchingOwaspTop10={false}
+ fetchingOwaspTop10-2021={false}
fetchingSansTop25={false}
fetchingSonarSourceSecurity={false}
onChange={props.onFilterChange}
owaspTop10={props.query.owaspTop10}
owaspTop10Open={!!props.openFacets.owaspTop10}
owaspTop10Stats={props.facets && props.facets.owaspTop10}
+ owaspTop10-2021={props.query['owaspTop10-2021']}
+ owaspTop10-2021Open={!!props.openFacets['owaspTop10-2021']}
+ owaspTop10-2021Stats={props.facets && props.facets['owaspTop10-2021']}
query={props.query}
sansTop25={props.query.sansTop25}
sansTop25Open={!!props.openFacets.sansTop25}
"inheritance": undefined,
"languages": Array [],
"owaspTop10": Array [],
+ "owaspTop10-2021": Array [],
"profile": undefined,
"repositories": Array [],
"ruleKey": undefined,
"inheritance": undefined,
"languages": Array [],
"owaspTop10": Array [],
+ "owaspTop10-2021": Array [],
"profile": undefined,
"repositories": Array [],
"ruleKey": undefined,
Object {
"languages": true,
"owaspTop10": false,
+ "owaspTop10-2021": false,
"sansTop25": false,
"sonarsourceSecurity": false,
"standards": false,
"inheritance": undefined,
"languages": Array [],
"owaspTop10": Array [],
+ "owaspTop10-2021": Array [],
"profile": undefined,
"repositories": Array [],
"ruleKey": undefined,
"inheritance": undefined,
"languages": Array [],
"owaspTop10": Array [],
+ "owaspTop10-2021": Array [],
"profile": undefined,
"repositories": Array [],
"ruleKey": undefined,
Object {
"languages": true,
"owaspTop10": false,
+ "owaspTop10-2021": false,
"sansTop25": false,
"sonarsourceSecurity": false,
"standards": false,
"inheritance": undefined,
"languages": Array [],
"owaspTop10": Array [],
+ "owaspTop10-2021": Array [],
"profile": undefined,
"repositories": Array [],
"ruleKey": undefined,
cweOpen={false}
fetchingCwe={false}
fetchingOwaspTop10={false}
+ fetchingOwaspTop10-2021={false}
fetchingSansTop25={false}
fetchingSonarSourceSecurity={false}
onChange={[MockFunction]}
onToggle={[MockFunction]}
open={false}
+ owaspTop10-2021Open={false}
owaspTop10Open={false}
query={Object {}}
sansTop25Open={false}
inheritance: RuleInheritance | undefined;
languages: string[];
owaspTop10: string[];
+ 'owaspTop10-2021': string[];
profile: string | undefined;
repositories: string[];
ruleKey: string | undefined;
inheritance: parseAsInheritance(query.inheritance),
languages: parseAsArray(query.languages, parseAsString),
owaspTop10: parseAsArray(query.owaspTop10, parseAsString),
+ 'owaspTop10-2021': parseAsArray(query['owaspTop10-2021'], parseAsString),
profile: parseAsOptionalString(query.qprofile),
repositories: parseAsArray(query.repositories, parseAsString),
ruleKey: parseAsOptionalString(query.rule_key),
is_template: serializeOptionalBoolean(query.template),
languages: serializeStringArray(query.languages),
owaspTop10: serializeStringArray(query.owaspTop10),
+ 'owaspTop10-2021': serializeStringArray(query['owaspTop10-2021']),
q: serializeString(query.searchQuery),
qprofile: serializeString(query.profile),
repositories: serializeStringArray(query.repositories),
'cwe',
'languages',
'owaspTop10',
+ 'owaspTop10-2021',
'repositories',
'sansTop25',
'severities',
function parseAsInheritance(value?: string): RuleInheritance | undefined {
if (value === 'INHERITED' || value === 'NONE' || value === 'OVERRIDES') {
return value;
- } else {
- return undefined;
}
+ return undefined;
}
function serializeInheritance(value: RuleInheritance | undefined): string | undefined {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
+import { renderOwaspTop102021Category } from '../../../helpers/security-standard';
+import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
+import AppContainer from '../components/AppContainer';
+
+jest.mock('../../../api/issues');
+
+let handler: IssuesServiceMock;
+
+beforeAll(() => {
+ handler = new IssuesServiceMock();
+});
+
+afterEach(() => handler.reset());
+
+jest.setTimeout(10_000);
+
+it('should support OWASP Top 10 version 2021', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+ await user.click(await screen.findByRole('link', { name: 'issues.facet.standards' }));
+ const owaspTop102021 = screen.getByRole('link', { name: 'issues.facet.owaspTop10_2021' });
+ expect(owaspTop102021).toBeInTheDocument();
+
+ await user.click(owaspTop102021);
+ await Promise.all(
+ handler.owasp2021FacetList().values.map(async ({ val }) => {
+ const standard = await handler.getStandards();
+ /* eslint-disable-next-line testing-library/render-result-naming-convention */
+ const linkName = renderOwaspTop102021Category(standard, val);
+ expect(await screen.findByRole('link', { name: linkName })).toBeInTheDocument();
+ })
+ );
+});
+
+function renderIssueApp() {
+ renderComponentApp('project/issues', AppContainer);
+}
myIssues: areMyIssuesSelected(props.location.query),
openFacets: {
owaspTop10: shouldOpenStandardsChildFacet({}, query, SecurityStandard.OWASP_TOP10),
+ 'owaspTop10-2021': shouldOpenStandardsChildFacet(
+ {},
+ query,
+ SecurityStandard.OWASP_TOP10_2021
+ ),
sansTop25: shouldOpenStandardsChildFacet({}, query, SecurityStandard.SANS_TOP25),
severities: true,
sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query),
const facets = requestFacets
? Object.keys(openFacets)
- .filter(facet => facet !== STANDARDS)
+ .filter(facet => facet !== STANDARDS && openFacets[facet])
.join(',')
: undefined;
if (myIssues) {
Object.assign(parameters, { assignees: '__me__' });
}
-
return this.props.fetchIssues(parameters);
};
}).state('openFacets')
).toEqual({
owaspTop10: false,
+ 'owaspTop10-2021': false,
sansTop25: false,
severities: true,
standards: false,
}).state('openFacets')
).toEqual({
owaspTop10: true,
+ 'owaspTop10-2021': false,
sansTop25: false,
severities: true,
standards: true,
openFacets={
Object {
"owaspTop10": false,
+ "owaspTop10-2021": false,
"sansTop25": false,
"severities": true,
"sonarsourceSecurity": false,
"issues": Array [],
"languages": Array [],
"owaspTop10": Array [],
+ "owaspTop10-2021": Array [],
"projects": Array [],
"resolutions": Array [],
"resolved": true,
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { Location } from '../../helpers/urls';
-import { RawQuery } from '../../types/types';
-import { areMyIssuesSelected, parseQuery, serializeQuery } from './utils';
-
-function parseHash(hash: string) {
- const query: RawQuery = {};
- const parts = hash.split('|');
- parts.forEach(part => {
- const tokens = part.split('=');
- if (tokens.length === 2) {
- const property = decodeURIComponent(tokens[0]);
- const value = decodeURIComponent(tokens[1]);
- if (property === 'assigned_to_me' && value === 'true') {
- query.myIssues = 'true';
- } else {
- query[property] = value;
- }
- }
- });
- return query;
-}
-
-export function onEnter(state: any, replace: (location: Location) => void) {
- const { hash } = window.location;
- if (hash.length > 1) {
- const query = parseHash(hash.substr(1));
- const normalizedQuery = {
- ...serializeQuery(parseQuery(query)),
- myIssues: areMyIssuesSelected(query) ? 'true' : undefined
- };
- replace({
- pathname: state.location.pathname,
- query: normalizedQuery
- });
- }
-}
cweStats={facets.cwe}
fetchingCwe={this.props.loadingFacets.cwe === true}
fetchingOwaspTop10={this.props.loadingFacets.owaspTop10 === true}
+ fetchingOwaspTop10-2021={this.props.loadingFacets['owaspTop10-2021'] === true}
fetchingSansTop25={this.props.loadingFacets.sansTop25 === true}
fetchingSonarSourceSecurity={this.props.loadingFacets.sonarsourceSecurity === true}
loadSearchResultCount={this.props.loadSearchResultCount}
owaspTop10={query.owaspTop10}
owaspTop10Open={!!openFacets.owaspTop10}
owaspTop10Stats={facets.owaspTop10}
+ owaspTop10-2021={query['owaspTop10-2021']}
+ owaspTop10-2021Open={!!openFacets['owaspTop10-2021']}
+ owaspTop10-2021Stats={facets['owaspTop10-2021']}
query={query}
sansTop25={query.sansTop25}
sansTop25Open={!!openFacets.sansTop25}
import {
getStandards,
renderCWECategory,
+ renderOwaspTop102021Category,
renderOwaspTop10Category,
renderSansTop25Category,
renderSonarSourceSecurityCategory
cweStats: Dict<number> | undefined;
fetchingCwe: boolean;
fetchingOwaspTop10: boolean;
+ 'fetchingOwaspTop10-2021': boolean;
fetchingSansTop25: boolean;
fetchingSonarSourceSecurity: boolean;
loadSearchResultCount?: (property: string, changes: Partial<Query>) => Promise<Facet>;
owaspTop10: string[];
owaspTop10Open: boolean;
owaspTop10Stats: Dict<number> | undefined;
+ 'owaspTop10-2021': string[];
+ 'owaspTop10-2021Open': boolean;
+ 'owaspTop10-2021Stats': Dict<number> | undefined;
query: Partial<Query>;
sansTop25: string[];
sansTop25Open: boolean;
standards: Standards;
}
-type StatsProp = 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats' | 'sonarsourceSecurityStats';
+type StatsProp =
+ | 'owaspTop10-2021Stats'
+ | 'owaspTop10Stats'
+ | 'cweStats'
+ | 'sansTop25Stats'
+ | 'sonarsourceSecurityStats';
type ValuesProp = StandardType;
export default class StandardFacet extends React.PureComponent<Props, State> {
mounted = false;
property = STANDARDS;
state: State = {
- standards: { owaspTop10: {}, sansTop25: {}, cwe: {}, sonarsourceSecurity: {} }
+ standards: {
+ owaspTop10: {},
+ 'owaspTop10-2021': {},
+ sansTop25: {},
+ cwe: {},
+ sonarsourceSecurity: {}
+ }
};
componentDidMount() {
if (
this.props.open ||
this.props.owaspTop10.length > 0 ||
+ this.props['owaspTop10-2021'].length > 0 ||
this.props.cwe.length > 0 ||
this.props.sansTop25.length > 0 ||
this.props.sonarsourceSecurity.length > 0
loadStandards = () => {
getStandards().then(
- ({ owaspTop10, sansTop25, cwe, sonarsourceSecurity }: Standards) => {
+ ({
+ 'owaspTop10-2021': owaspTop102021,
+ owaspTop10,
+ sansTop25,
+ cwe,
+ sonarsourceSecurity
+ }: Standards) => {
if (this.mounted) {
- this.setState({ standards: { owaspTop10, sansTop25, cwe, sonarsourceSecurity } });
+ this.setState({
+ standards: {
+ 'owaspTop10-2021': owaspTop102021,
+ owaspTop10,
+ sansTop25,
+ cwe,
+ sonarsourceSecurity
+ }
+ });
}
},
() => {}
...this.props.owaspTop10.map(item =>
renderOwaspTop10Category(this.state.standards, item, true)
),
+ ...this.props['owaspTop10-2021'].map(item =>
+ renderOwaspTop102021Category(this.state.standards, item, true)
+ ),
...this.props.sansTop25.map(item =>
renderSansTop25Category(this.state.standards, item, true)
),
this.props.onToggle('owaspTop10');
};
+ handleOwaspTop102021HeaderClick = () => {
+ this.props.onToggle('owaspTop10-2021');
+ };
+
handleSansTop25HeaderClick = () => {
this.props.onToggle('sansTop25');
};
this.props.onChange({
[this.property]: [],
owaspTop10: [],
+ 'owaspTop10-2021': [],
sansTop25: [],
cwe: [],
sonarsourceSecurity: []
this.handleItemClick(SecurityStandard.OWASP_TOP10, itemValue, multiple);
};
+ handleOwaspTop102021ItemClick = (itemValue: string, multiple: boolean) => {
+ this.handleItemClick(SecurityStandard.OWASP_TOP10_2021, itemValue, multiple);
+ };
+
handleSansTop25ItemClick = (itemValue: string, multiple: boolean) => {
this.handleItemClick(SecurityStandard.SANS_TOP25, itemValue, multiple);
};
);
}
- renderOwaspTop10Hint() {
- return this.renderHint('owaspTop10Stats', SecurityStandard.OWASP_TOP10);
+ renderOwaspTop102021List() {
+ return this.renderList(
+ 'owaspTop10-2021Stats',
+ SecurityStandard.OWASP_TOP10_2021,
+ renderOwaspTop102021Category,
+ this.handleOwaspTop102021ItemClick
+ );
}
renderSansTop25List() {
);
}
- renderSansTop25Hint() {
- return this.renderHint('sansTop25Stats', SecurityStandard.SANS_TOP25);
- }
-
renderSonarSourceSecurityList() {
return this.renderList(
'sonarsourceSecurityStats',
);
}
+ renderOwaspTop10Hint() {
+ return this.renderHint('owaspTop10Stats', SecurityStandard.OWASP_TOP10);
+ }
+
+ renderOwaspTop102021Hint() {
+ return this.renderHint('owaspTop10-2021Stats', SecurityStandard.OWASP_TOP10_2021);
+ }
+
+ renderSansTop25Hint() {
+ return this.renderHint('sansTop25Stats', SecurityStandard.SANS_TOP25);
+ }
+
renderSonarSourceSecurityHint() {
return this.renderHint('sonarsourceSecurityStats', SecurityStandard.SONARSOURCE);
}
</>
)}
</FacetBox>
+ <FacetBox className="is-inner" property={SecurityStandard.OWASP_TOP10_2021}>
+ <FacetHeader
+ fetching={this.props['fetchingOwaspTop10-2021']}
+ name={translate('issues.facet.owaspTop10_2021')}
+ onClick={this.handleOwaspTop102021HeaderClick}
+ open={this.props['owaspTop10-2021Open']}
+ values={this.props['owaspTop10-2021'].map(item =>
+ renderOwaspTop102021Category(this.state.standards, item)
+ )}
+ />
+ {this.props['owaspTop10-2021Open'] && (
+ <>
+ {this.renderOwaspTop102021List()}
+ {this.renderOwaspTop102021Hint()}
+ </>
+ )}
+ </FacetBox>
<FacetBox className="is-inner" property={SecurityStandard.OWASP_TOP10}>
<FacetHeader
fetching={this.props.fetchingOwaspTop10}
expect(onChange).toBeCalledWith({
cwe: [],
owaspTop10: [],
+ 'owaspTop10-2021': [],
sansTop25: [],
sonarsourceSecurity: [],
standards: []
const wrapper = shallowRender({
open: true,
owaspTop10: ['a1', 'a3'],
+ 'owaspTop10-2021': ['a1', 'a2'],
sansTop25: ['risky-resource', 'foo'],
cwe: ['42', '1111', 'unknown'],
sonarsourceSecurity: ['sql-injection', 'others']
'Others',
'OWASP A1 - a1 title',
'OWASP A3',
+ 'OWASP A1 - a1 title',
+ 'OWASP A2',
'SANS Risky Resource Management',
'SANS foo',
'CWE-42 - cwe-42 title',
'Unknown CWE'
]);
checkValues('owaspTop10', ['A1 - a1 title', 'A3']);
+ checkValues('owaspTop10-2021', ['A1 - a1 title', 'A2']);
checkValues('sansTop25', ['Risky Resource Management', 'foo']);
checkValues('sonarsourceSecurity', ['SQL Injection', 'Others']);
cweStats={{}}
fetchingCwe={false}
fetchingOwaspTop10={false}
+ fetchingOwaspTop10-2021={false}
fetchingSansTop25={false}
fetchingSonarSourceSecurity={false}
loadSearchResultCount={jest.fn()}
owaspTop10={[]}
owaspTop10Open={false}
owaspTop10Stats={{}}
+ owaspTop10-2021={[]}
+ owaspTop10-2021Open={false}
+ owaspTop10-2021Stats={{}}
query={{} as Query}
sansTop25={[]}
sansTop25Open={false}
wrapper.setState({
standards: {
owaspTop10: { a1: { title: 'a1 title' } },
+ 'owaspTop10-2021': { a1: { title: 'a1 title' } },
sansTop25: { 'risky-resource': { title: 'Risky Resource Management' } },
cwe: { 42: { title: 'cwe-42 title' }, unknown: { title: 'Unknown CWE' } },
sonarsourceSecurity: {
values={1}
/>
</FacetBox>
+ <FacetBox
+ className="is-inner"
+ property="owaspTop10-2021"
+ >
+ <FacetHeader
+ fetching={false}
+ name="issues.facet.owaspTop10_2021"
+ onClick={[Function]}
+ open={false}
+ values={Array []}
+ />
+ </FacetBox>
<FacetBox
className="is-inner"
property="owaspTop10"
issues: string[];
languages: string[];
owaspTop10: string[];
+ 'owaspTop10-2021': string[];
projects: string[];
resolutions: string[];
resolved: boolean;
issues: parseAsArray(query.issues, parseAsString),
languages: parseAsArray(query.languages, parseAsString),
owaspTop10: parseAsArray(query.owaspTop10, parseAsString),
+ 'owaspTop10-2021': parseAsArray(query['owaspTop10-2021'], parseAsString),
projects: parseAsArray(query.projects, parseAsString),
resolutions: parseAsArray(query.resolutions, parseAsString),
resolved: parseAsBoolean(query.resolved),
issues: serializeStringArray(query.issues),
languages: serializeStringArray(query.languages),
owaspTop10: serializeStringArray(query.owaspTop10),
+ 'owaspTop10-2021': serializeStringArray(query['owaspTop10-2021']),
projects: serializeStringArray(query.projects),
resolutions: serializeStringArray(query.resolutions),
resolved: query.resolved ? undefined : 'false',
selectedHotspot: undefined,
standards: {
[SecurityStandard.OWASP_TOP10]: {},
+ [SecurityStandard.OWASP_TOP10_2021]: {},
[SecurityStandard.SANS_TOP25]: {},
[SecurityStandard.SONARSOURCE]: {},
[SecurityStandard.CWE]: {}
Object {
"cwe": Object {},
"owaspTop10": Object {},
+ "owaspTop10-2021": Object {},
"sansTop25": Object {},
"sonarsourceSecurity": Object {},
}
"title": "Sensitive Data Exposure",
},
},
+ "owaspTop10-2021": Object {
+ "a1": Object {
+ "title": "Injection",
+ },
+ "a2": Object {
+ "title": "Broken Authentication",
+ },
+ "a3": Object {
+ "title": "Sensitive Data Exposure",
+ },
+ },
"sansTop25": Object {
"insecure-interaction": Object {
"title": "Insecure Interaction Between Components",
"title": "Sensitive Data Exposure",
},
},
+ "owaspTop10-2021": Object {
+ "a1": Object {
+ "title": "Injection",
+ },
+ "a2": Object {
+ "title": "Broken Authentication",
+ },
+ "a3": Object {
+ "title": "Sensitive Data Exposure",
+ },
+ },
"sansTop25": Object {
"insecure-interaction": Object {
"title": "Insecure Interaction Between Components",
a1: { title: 'A1 - SQL Injection' },
a3: { title: 'A3 - Sensitive Data Exposure' }
},
+ 'owaspTop10-2021': {
+ a1: { title: 'A1 - SQL Injection' },
+ a3: { title: 'A3 - Sensitive Data Exposure' }
+ },
sansTop25: {},
sonarsourceSecurity: {}
}}
import { flatten, groupBy, sortBy } from 'lodash';
import {
renderCWECategory,
+ renderOwaspTop102021Category,
renderOwaspTop10Category,
renderSansTop25Category,
renderSonarSourceSecurityCategory
export const SECURITY_STANDARDS = [
SecurityStandard.SONARSOURCE,
SecurityStandard.OWASP_TOP10,
+ SecurityStandard.OWASP_TOP10_2021,
SecurityStandard.SANS_TOP25,
SecurityStandard.CWE
];
export const SECURITY_STANDARD_RENDERER = {
[SecurityStandard.OWASP_TOP10]: renderOwaspTop10Category,
+ [SecurityStandard.OWASP_TOP10_2021]: renderOwaspTop102021Category,
[SecurityStandard.SANS_TOP25]: renderSansTop25Category,
[SecurityStandard.SONARSOURCE]: renderSonarSourceSecurityCategory,
[SecurityStandard.CWE]: renderCWECategory
import { Standards } from '../../types/security';
import {
renderCWECategory,
+ renderOwaspTop102021Category,
renderOwaspTop10Category,
renderSansTop25Category,
renderSonarSourceSecurityCategory
}
},
owaspTop10: {},
+ 'owaspTop10-2021': {},
sansTop25: {},
sonarsourceSecurity: {}
};
title: 'Injection'
}
},
+ 'owaspTop10-2021': {},
sansTop25: {},
sonarsourceSecurity: {}
};
});
});
+describe('renderOwaspTop102021Category', () => {
+ const standards: Standards = {
+ cwe: {},
+ owaspTop10: {},
+ 'owaspTop10-2021': {
+ a1: {
+ title: 'Injection'
+ }
+ },
+ sansTop25: {},
+ sonarsourceSecurity: {}
+ };
+ it('should render owasp categories correctly', () => {
+ expect(renderOwaspTop102021Category(standards, 'a1')).toEqual('A1 - Injection');
+ expect(renderOwaspTop102021Category(standards, 'a1', true)).toEqual('OWASP A1 - Injection');
+ expect(renderOwaspTop102021Category(standards, 'a2')).toEqual('A2');
+ expect(renderOwaspTop102021Category(standards, 'a2', true)).toEqual('OWASP A2');
+ });
+});
+
describe('renderSansTop25Category', () => {
const standards: Standards = {
cwe: {},
owaspTop10: {},
+ 'owaspTop10-2021': {},
sansTop25: {
'insecure-interaction': {
title: 'Insecure Interaction Between Components'
const standards: Standards = {
cwe: {},
owaspTop10: {},
+ 'owaspTop10-2021': {},
sansTop25: {},
sonarsourceSecurity: {
xss: {
title: 'Sensitive Data Exposure'
}
},
+ 'owaspTop10-2021': {
+ a1: {
+ title: 'Injection'
+ },
+ a2: {
+ title: 'Broken Authentication'
+ },
+ a3: {
+ title: 'Sensitive Data Exposure'
+ }
+ },
sansTop25: {
'insecure-interaction': {
title: 'Insecure Interaction Between Components'
category: string,
withPrefix = false
): string {
- const record = standards.owaspTop10[category];
+ return renderOwaspCategory('owaspTop10', standards, category, withPrefix);
+}
+
+export function renderOwaspTop102021Category(
+ standards: Standards,
+ category: string,
+ withPrefix = false
+): string {
+ return renderOwaspCategory('owaspTop10-2021', standards, category, withPrefix);
+}
+
+function renderOwaspCategory(
+ type: 'owaspTop10' | 'owaspTop10-2021',
+ standards: Standards,
+ category: string,
+ withPrefix: boolean
+) {
+ const record = standards[type][category];
if (!record) {
return addPrefix(category.toUpperCase(), 'OWASP', withPrefix);
- } else {
- return addPrefix(`${category.toUpperCase()} - ${record.title}`, 'OWASP', withPrefix);
}
+ return addPrefix(`${category.toUpperCase()} - ${record.title}`, 'OWASP', withPrefix);
}
export function renderSansTop25Category(
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
-import { createMemoryHistory, RouteConfig, Router } from 'react-router';
+import { createMemoryHistory, Route, RouteComponent, RouteConfig, Router } from 'react-router';
import { Store } from 'redux';
import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
+import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider';
import { MetricsContext } from '../app/components/metrics/MetricsContext';
import getStore from '../app/utils/getStore';
import { RouteWithChildRoutes } from '../app/utils/startReactApp';
import { Store as State } from '../store/rootReducer';
import { AppState } from '../types/appstate';
import { Dict, Metric } from '../types/types';
+import { CurrentUser } from '../types/users';
import { DEFAULT_METRICS } from './mocks/metrics';
-import { mockAppState } from './testMocks';
+import { mockAppState, mockCurrentUser } from './testMocks';
interface RenderContext {
metrics?: Dict<Metric>;
store?: Store<State, any>;
history?: History;
appState?: AppState;
+ currentUser?: CurrentUser;
+}
+
+export function renderComponentApp(
+ indexPath: string,
+ component: RouteComponent,
+ context: RenderContext = {}
+): RenderResult {
+ return renderRoutedApp(<Route path={indexPath} component={component} />, indexPath, context);
}
export function renderApp(
indexPath: string,
routes: RouteConfig,
+ context: RenderContext
+): RenderResult {
+ return renderRoutedApp(
+ <RouteWithChildRoutes path={indexPath} childRoutes={routes} />,
+ indexPath,
+ context
+ );
+}
+
+function renderRoutedApp(
+ children: React.ReactElement,
+ indexPath: string,
{
+ currentUser = mockCurrentUser(),
metrics = DEFAULT_METRICS,
store = getStore(),
appState = mockAppState(),
<IntlProvider defaultLocale="en" locale="en">
<MetricsContext.Provider value={metrics}>
<Provider store={store}>
- <AppStateContextProvider appState={appState}>
- <Router history={history}>
- <RouteWithChildRoutes path={indexPath} childRoutes={routes} />
- </Router>
- </AppStateContextProvider>
+ <CurrentUserContextProvider currentUser={currentUser}>
+ <AppStateContextProvider appState={appState}>
+ <Router history={history}>{children}</Router>
+ </AppStateContextProvider>
+ </CurrentUserContextProvider>
</Provider>
</MetricsContext.Provider>
</IntlProvider>
import { Dict } from './types';
export enum SecurityStandard {
+ OWASP_TOP10_2021 = 'owaspTop10-2021',
OWASP_TOP10 = 'owaspTop10',
SANS_TOP25 = 'sansTop25',
SONARSOURCE = 'sonarsourceSecurity',
issues.facet.mode.count=Issues
issues.facet.mode.effort=Effort
issues.facet.standards=Security Category
-issues.facet.owaspTop10=OWASP Top 10
+issues.facet.owaspTop10=OWASP Top 10 2017
+issues.facet.owaspTop10_2021=OWASP Top 10 2021
issues.facet.sansTop25=SANS Top 25
issues.facet.sonarsourceSecurity=SonarSource
issues.facet.cwe=CWE