diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
20 files changed, 235 insertions, 69 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx index 53a3f6da600..a9a5d6604c9 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx @@ -108,6 +108,11 @@ export class App extends React.PureComponent<Props, State> { 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), diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx index c08e02cc29e..3b7578e05e3 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx @@ -110,6 +110,7 @@ export default function FacetsList(props: FacetsListProps) { cweStats={props.facets && props.facets.cwe} fetchingCwe={false} fetchingOwaspTop10={false} + fetchingOwaspTop10-2021={false} fetchingSansTop25={false} fetchingSonarSourceSecurity={false} onChange={props.onFilterChange} @@ -118,6 +119,9 @@ export default function FacetsList(props: FacetsListProps) { 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} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/App-test.tsx.snap index 4d1707ff0da..cb55971f54b 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/App-test.tsx.snap @@ -16,6 +16,7 @@ exports[`renderBulkButton should show bulk change button when user has edit righ "inheritance": undefined, "languages": Array [], "owaspTop10": Array [], + "owaspTop10-2021": Array [], "profile": undefined, "repositories": Array [], "ruleKey": undefined, @@ -80,6 +81,7 @@ exports[`renderBulkButton should show bulk change button when user has global ad "inheritance": undefined, "languages": Array [], "owaspTop10": Array [], + "owaspTop10-2021": Array [], "profile": undefined, "repositories": Array [], "ruleKey": undefined, @@ -139,6 +141,7 @@ exports[`should render correctly: loaded (ScreenPositionHelper) 1`] = ` Object { "languages": true, "owaspTop10": false, + "owaspTop10-2021": false, "sansTop25": false, "sonarsourceSecurity": false, "standards": false, @@ -155,6 +158,7 @@ exports[`should render correctly: loaded (ScreenPositionHelper) 1`] = ` "inheritance": undefined, "languages": Array [], "owaspTop10": Array [], + "owaspTop10-2021": Array [], "profile": undefined, "repositories": Array [], "ruleKey": undefined, @@ -230,6 +234,7 @@ exports[`should render correctly: loaded 1`] = ` "inheritance": undefined, "languages": Array [], "owaspTop10": Array [], + "owaspTop10-2021": Array [], "profile": undefined, "repositories": Array [], "ruleKey": undefined, @@ -434,6 +439,7 @@ exports[`should render correctly: open rule (ScreenPositionHelper) 1`] = ` Object { "languages": true, "owaspTop10": false, + "owaspTop10-2021": false, "sansTop25": false, "sonarsourceSecurity": false, "standards": false, @@ -450,6 +456,7 @@ exports[`should render correctly: open rule (ScreenPositionHelper) 1`] = ` "inheritance": undefined, "languages": Array [], "owaspTop10": Array [], + "owaspTop10-2021": Array [], "profile": undefined, "repositories": Array [], "ruleKey": undefined, diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/FacetsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/FacetsList-test.tsx.snap index 7a490ced59c..809ba918cf2 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/FacetsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/FacetsList-test.tsx.snap @@ -38,11 +38,13 @@ exports[`should render correctly 1`] = ` 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} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/query.ts b/server/sonar-web/src/main/js/apps/coding-rules/query.ts index bd10f04faf9..ddbe9a742bd 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/query.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/query.ts @@ -41,6 +41,7 @@ export interface Query { inheritance: RuleInheritance | undefined; languages: string[]; owaspTop10: string[]; + 'owaspTop10-2021': string[]; profile: string | undefined; repositories: string[]; ruleKey: string | undefined; @@ -85,6 +86,7 @@ export function parseQuery(query: RawQuery): Query { 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), @@ -110,6 +112,7 @@ export function serializeQuery(query: Query): RawQuery { 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), @@ -133,6 +136,7 @@ export function shouldRequestFacet(facet: string): facet is FacetKey { 'cwe', 'languages', 'owaspTop10', + 'owaspTop10-2021', 'repositories', 'sansTop25', 'severities', @@ -164,9 +168,8 @@ export function hasRuleKey(query: RawQuery) { 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 { diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx new file mode 100644 index 00000000000..c703fd77313 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx @@ -0,0 +1,59 @@ +/* + * 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); +} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 848b4da99fe..92db8e0acc8 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -153,6 +153,11 @@ export default class App extends React.PureComponent<Props, State> { 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), @@ -406,7 +411,7 @@ export default class App extends React.PureComponent<Props, State> { const facets = requestFacets ? Object.keys(openFacets) - .filter(facet => facet !== STANDARDS) + .filter(facet => facet !== STANDARDS && openFacets[facet]) .join(',') : undefined; @@ -432,7 +437,6 @@ export default class App extends React.PureComponent<Props, State> { if (myIssues) { Object.assign(parameters, { assignees: '__me__' }); } - return this.props.fetchIssues(parameters); }; diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx index 610b1b8e19f..0e4ed2e7a22 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx @@ -363,6 +363,7 @@ it('should display the right facets open', () => { }).state('openFacets') ).toEqual({ owaspTop10: false, + 'owaspTop10-2021': false, sansTop25: false, severities: true, standards: false, @@ -375,6 +376,7 @@ it('should display the right facets open', () => { }).state('openFacets') ).toEqual({ owaspTop10: true, + 'owaspTop10-2021': false, sansTop25: false, severities: true, standards: true, diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap index 073c704f38a..5af18892050 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap @@ -99,6 +99,7 @@ exports[`should show warnning when not all projects are accessible 1`] = ` openFacets={ Object { "owaspTop10": false, + "owaspTop10-2021": false, "sansTop25": false, "severities": true, "sonarsourceSecurity": false, @@ -121,6 +122,7 @@ exports[`should show warnning when not all projects are accessible 1`] = ` "issues": Array [], "languages": Array [], "owaspTop10": Array [], + "owaspTop10-2021": Array [], "projects": Array [], "resolutions": Array [], "resolved": true, diff --git a/server/sonar-web/src/main/js/apps/issues/redirects.ts b/server/sonar-web/src/main/js/apps/issues/redirects.ts deleted file mode 100644 index 28dc59a5e6b..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/redirects.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 - }); - } -} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx index e15af101eac..dc69ae3909a 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx @@ -180,6 +180,7 @@ export class Sidebar extends React.PureComponent<Props> { 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} @@ -189,6 +190,9 @@ export class Sidebar extends React.PureComponent<Props> { 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} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx index e0eb8d46d25..6f1debef576 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx @@ -30,6 +30,7 @@ import { highlightTerm } from '../../../helpers/search'; import { getStandards, renderCWECategory, + renderOwaspTop102021Category, renderOwaspTop10Category, renderSansTop25Category, renderSonarSourceSecurityCategory @@ -45,6 +46,7 @@ interface Props { cweStats: Dict<number> | undefined; fetchingCwe: boolean; fetchingOwaspTop10: boolean; + 'fetchingOwaspTop10-2021': boolean; fetchingSansTop25: boolean; fetchingSonarSourceSecurity: boolean; loadSearchResultCount?: (property: string, changes: Partial<Query>) => Promise<Facet>; @@ -54,6 +56,9 @@ interface Props { 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; @@ -67,14 +72,25 @@ interface State { 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() { @@ -84,6 +100,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> { 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 @@ -104,9 +121,23 @@ export default class StandardFacet extends React.PureComponent<Props, State> { 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 + } + }); } }, () => {} @@ -121,6 +152,9 @@ export default class StandardFacet extends React.PureComponent<Props, State> { ...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) ), @@ -136,6 +170,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { this.props.onToggle('owaspTop10'); }; + handleOwaspTop102021HeaderClick = () => { + this.props.onToggle('owaspTop10-2021'); + }; + handleSansTop25HeaderClick = () => { this.props.onToggle('sansTop25'); }; @@ -148,6 +186,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> { this.props.onChange({ [this.property]: [], owaspTop10: [], + 'owaspTop10-2021': [], sansTop25: [], cwe: [], sonarsourceSecurity: [] @@ -172,6 +211,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { 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); }; @@ -265,8 +308,13 @@ export default class StandardFacet extends React.PureComponent<Props, State> { ); } - renderOwaspTop10Hint() { - return this.renderHint('owaspTop10Stats', SecurityStandard.OWASP_TOP10); + renderOwaspTop102021List() { + return this.renderList( + 'owaspTop10-2021Stats', + SecurityStandard.OWASP_TOP10_2021, + renderOwaspTop102021Category, + this.handleOwaspTop102021ItemClick + ); } renderSansTop25List() { @@ -278,10 +326,6 @@ export default class StandardFacet extends React.PureComponent<Props, State> { ); } - renderSansTop25Hint() { - return this.renderHint('sansTop25Stats', SecurityStandard.SANS_TOP25); - } - renderSonarSourceSecurityList() { return this.renderList( 'sonarsourceSecurityStats', @@ -291,6 +335,18 @@ export default class StandardFacet extends React.PureComponent<Props, State> { ); } + 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); } @@ -315,6 +371,23 @@ export default class StandardFacet extends React.PureComponent<Props, State> { </> )} </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} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx index 2893c038f93..a2b9e7cd062 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx @@ -78,6 +78,7 @@ it('should clear standards facet', () => { expect(onChange).toBeCalledWith({ cwe: [], owaspTop10: [], + 'owaspTop10-2021': [], sansTop25: [], sonarsourceSecurity: [], standards: [] @@ -161,6 +162,7 @@ it('should display correct selection', () => { const wrapper = shallowRender({ open: true, owaspTop10: ['a1', 'a3'], + 'owaspTop10-2021': ['a1', 'a2'], sansTop25: ['risky-resource', 'foo'], cwe: ['42', '1111', 'unknown'], sonarsourceSecurity: ['sql-injection', 'others'] @@ -170,6 +172,8 @@ it('should display correct selection', () => { 'Others', 'OWASP A1 - a1 title', 'OWASP A3', + 'OWASP A1 - a1 title', + 'OWASP A2', 'SANS Risky Resource Management', 'SANS foo', 'CWE-42 - cwe-42 title', @@ -177,6 +181,7 @@ it('should display correct selection', () => { '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']); @@ -198,6 +203,7 @@ function shallowRender(props: Partial<StandardFacet['props']> = {}) { cweStats={{}} fetchingCwe={false} fetchingOwaspTop10={false} + fetchingOwaspTop10-2021={false} fetchingSansTop25={false} fetchingSonarSourceSecurity={false} loadSearchResultCount={jest.fn()} @@ -207,6 +213,9 @@ function shallowRender(props: Partial<StandardFacet['props']> = {}) { owaspTop10={[]} owaspTop10Open={false} owaspTop10Stats={{}} + owaspTop10-2021={[]} + owaspTop10-2021Open={false} + owaspTop10-2021Stats={{}} query={{} as Query} sansTop25={[]} sansTop25Open={false} @@ -220,6 +229,7 @@ function shallowRender(props: Partial<StandardFacet['props']> = {}) { 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: { diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap index a38caf5b816..935c38c00b7 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap @@ -92,6 +92,18 @@ exports[`should render sub-facets 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" > <FacetHeader diff --git a/server/sonar-web/src/main/js/apps/issues/utils.ts b/server/sonar-web/src/main/js/apps/issues/utils.ts index 7b478ec8559..4ce712a7825 100644 --- a/server/sonar-web/src/main/js/apps/issues/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/utils.ts @@ -53,6 +53,7 @@ export interface Query { issues: string[]; languages: string[]; owaspTop10: string[]; + 'owaspTop10-2021': string[]; projects: string[]; resolutions: string[]; resolved: boolean; @@ -95,6 +96,7 @@ export function parseQuery(query: RawQuery): Query { 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), @@ -132,6 +134,7 @@ export function serializeQuery(query: Query): RawQuery { 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', diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx index 50fa7a08089..f8a2a7dbe5f 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx @@ -98,6 +98,7 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { selectedHotspot: undefined, standards: { [SecurityStandard.OWASP_TOP10]: {}, + [SecurityStandard.OWASP_TOP10_2021]: {}, [SecurityStandard.SANS_TOP25]: {}, [SecurityStandard.SONARSOURCE]: {}, [SecurityStandard.CWE]: {} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap index 1762e1a557e..2ab1904234a 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap @@ -58,6 +58,7 @@ exports[`should render correctly 1`] = ` Object { "cwe": Object {}, "owaspTop10": Object {}, + "owaspTop10-2021": Object {}, "sansTop25": Object {}, "sonarsourceSecurity": Object {}, } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap index fa7b43b13fa..6012cd9ffa7 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap @@ -157,6 +157,17 @@ exports[`should render correctly when filtered by category or cwe: category 1`] "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", @@ -278,6 +289,17 @@ exports[`should render correctly when filtered by category or cwe: cwe 1`] = ` "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", diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx index a74de8f1277..3629750ec3f 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx @@ -73,6 +73,10 @@ function shallowRender(props: Partial<HotspotSimpleListProps> = {}) { 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: {} }} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts index ca95c095990..b0275f40751 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts +++ b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts @@ -20,6 +20,7 @@ import { flatten, groupBy, sortBy } from 'lodash'; import { renderCWECategory, + renderOwaspTop102021Category, renderOwaspTop10Category, renderSansTop25Category, renderSonarSourceSecurityCategory @@ -47,12 +48,14 @@ export const RISK_EXPOSURE_LEVELS = [RiskExposure.HIGH, RiskExposure.MEDIUM, Ris 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 |