interface State {
filterByCategory?: { standard: SecurityStandard; category: string };
+ filterByCWE?: string;
filters: HotspotFilters;
hotspotKeys?: string[];
hotspots: RawHotspot[];
? (location.query.hotspots as string).split(',')
: undefined;
- const standard = SECURITY_STANDARDS.find(stnd => location.query[stnd] !== undefined);
+ const standard = SECURITY_STANDARDS.find(
+ stnd => stnd !== SecurityStandard.CWE && location.query[stnd] !== undefined
+ );
const filterByCategory = standard
? { standard, category: location.query[standard] }
: undefined;
- this.setState({ filterByCategory, hotspotKeys });
+ const filterByCWE: string | undefined = location.query.cwe;
+
+ this.setState({ filterByCategory, filterByCWE, hotspotKeys });
if (hotspotKeys && hotspotKeys.length > 0) {
return getSecurityHotspotList(hotspotKeys, {
});
}
- if (filterByCategory) {
+ if (filterByCategory || filterByCWE) {
+ const hotspotFilters: T.Dict<string> = {};
+
+ if (filterByCategory) {
+ hotspotFilters[filterByCategory.standard] = filterByCategory.category;
+ }
+ if (filterByCWE) {
+ hotspotFilters[SecurityStandard.CWE] = filterByCWE;
+ }
+
return getSecurityHotspots({
- [filterByCategory.standard]: filterByCategory.category,
+ ...hotspotFilters,
projectKey: component.key,
p: page,
ps: PAGE_SIZE,
query: {
...this.props.location.query,
hotspots: undefined,
+ [SecurityStandard.CWE]: undefined,
[SecurityStandard.OWASP_TOP10]: undefined,
[SecurityStandard.SANS_TOP25]: undefined,
[SecurityStandard.SONARSOURCE]: undefined
const { branchLike, component } = this.props;
const {
filterByCategory,
+ filterByCWE,
filters,
hotspotKeys,
hotspots,
component={component}
filters={filters}
filterByCategory={filterByCategory}
+ filterByCWE={filterByCWE}
hotspots={hotspots}
hotspotsReviewedMeasure={hotspotsReviewedMeasure}
hotspotsTotal={hotspotsTotal}
isStaticListOfHotspots={Boolean(
- (hotspotKeys && hotspotKeys.length > 0) || filterByCategory
+ (hotspotKeys && hotspotKeys.length > 0) || filterByCategory || filterByCWE
)}
loading={loading}
loadingMeasure={loadingMeasure}
standard: SecurityStandard;
category: string;
};
+ filterByCWE?: string;
filters: HotspotFilters;
hotspots: RawHotspot[];
hotspotsReviewedMeasure?: string;
branchLike,
component,
filterByCategory,
+ filterByCWE,
filters,
hotspots,
hotspotsReviewedMeasure,
{({ top }) => (
<div className="layout-page-side" ref={scrollableRef} style={{ top }}>
<div className="layout-page-side-inner">
- {filterByCategory ? (
+ {filterByCategory || filterByCWE ? (
<HotspotSimpleList
filterByCategory={filterByCategory}
+ filterByCWE={filterByCWE}
hotspots={hotspots}
hotspotsTotal={hotspotsTotal}
loadingMore={loadingMore}
);
});
+it('should handle cwe request', () => {
+ (getStandards as jest.Mock).mockResolvedValue(mockStandards());
+ (getMeasures as jest.Mock).mockResolvedValue([{ value: '86.6' }]);
+
+ shallowRender({
+ location: mockLocation({ query: { [SecurityStandard.CWE]: '1004' } })
+ });
+
+ expect(getSecurityHotspots).toBeCalledWith(
+ expect.objectContaining({ [SecurityStandard.CWE]: '1004' })
+ );
+});
+
it('should load data correctly when hotspot key list is forced', async () => {
const hotspots = [
mockRawHotspot({ key: 'test1' }),
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import { mockRawHotspot, mockStandards } from '../../../helpers/mocks/security-hotspots';
import { mockComponent } from '../../../helpers/testMocks';
+import { SecurityStandard } from '../../../types/security';
import { HotspotStatusFilter } from '../../../types/security-hotspots';
import FilterBar from '../components/FilterBar';
import SecurityHotspotsAppRenderer, {
).toMatchSnapshot();
});
+it('should render correctly when filtered by category or cwe', () => {
+ const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })];
+
+ expect(
+ shallowRender({ filterByCWE: '327', hotspots, hotspotsTotal: 2, selectedHotspot: hotspots[0] })
+ .find(ScreenPositionHelper)
+ .dive()
+ ).toMatchSnapshot('cwe');
+ expect(
+ shallowRender({
+ filterByCategory: { category: 'a1', standard: SecurityStandard.OWASP_TOP10 },
+ hotspots,
+ hotspotsTotal: 2,
+ selectedHotspot: hotspots[0]
+ })
+ .find(ScreenPositionHelper)
+ .dive()
+ ).toMatchSnapshot('category');
+});
+
it('should properly propagate the "show all" call', () => {
const onShowAllHotspots = jest.fn();
const wrapper = shallowRender({ onShowAllHotspots });
</div>
`;
+exports[`should render correctly when filtered by category or cwe: category 1`] = `
+<div
+ className="layout-page-side"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+>
+ <div
+ className="layout-page-side-inner"
+ >
+ <HotspotSimpleList
+ filterByCategory={
+ Object {
+ "category": "a1",
+ "standard": "owaspTop10",
+ }
+ }
+ hotspots={
+ Array [
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h2",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ ]
+ }
+ hotspotsTotal={2}
+ loadingMore={false}
+ onHotspotClick={[MockFunction]}
+ onLoadMore={[MockFunction]}
+ selectedHotspot={
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ }
+ }
+ standards={
+ Object {
+ "cwe": Object {
+ "1004": Object {
+ "title": "Sensitive Cookie Without 'HttpOnly' Flag",
+ },
+ "unknown": Object {
+ "title": "No CWE associated",
+ },
+ },
+ "owaspTop10": 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",
+ },
+ "porous-defenses": Object {
+ "title": "Porous Defenses",
+ },
+ "risky-resource": Object {
+ "title": "Risky Resource Management",
+ },
+ },
+ "sonarsourceSecurity": Object {
+ "buffer-overflow": Object {
+ "title": "Buffer Overflow",
+ },
+ "rce": Object {
+ "title": "Code Injection (RCE)",
+ },
+ "sql-injection": Object {
+ "title": "SQL Injection",
+ },
+ },
+ }
+ }
+ />
+ </div>
+</div>
+`;
+
+exports[`should render correctly when filtered by category or cwe: cwe 1`] = `
+<div
+ className="layout-page-side"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+>
+ <div
+ className="layout-page-side-inner"
+ >
+ <HotspotSimpleList
+ filterByCWE="327"
+ hotspots={
+ Array [
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h2",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ },
+ ]
+ }
+ hotspotsTotal={2}
+ loadingMore={false}
+ onHotspotClick={[MockFunction]}
+ onLoadMore={[MockFunction]}
+ selectedHotspot={
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ }
+ }
+ standards={
+ Object {
+ "cwe": Object {
+ "1004": Object {
+ "title": "Sensitive Cookie Without 'HttpOnly' Flag",
+ },
+ "unknown": Object {
+ "title": "No CWE associated",
+ },
+ },
+ "owaspTop10": 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",
+ },
+ "porous-defenses": Object {
+ "title": "Porous Defenses",
+ },
+ "risky-resource": Object {
+ "title": "Risky Resource Management",
+ },
+ },
+ "sonarsourceSecurity": Object {
+ "buffer-overflow": Object {
+ "title": "Buffer Overflow",
+ },
+ "rce": Object {
+ "title": "Code Injection (RCE)",
+ },
+ "sql-injection": Object {
+ "title": "SQL Injection",
+ },
+ },
+ }
+ }
+ />
+ </div>
+</div>
+`;
+
exports[`should render correctly with hotspots 1`] = `
<div
id="security_hotspots"
import HotspotListItem from './HotspotListItem';
export interface HotspotSimpleListProps {
- filterByCategory: {
+ filterByCategory?: {
standard: SecurityStandard;
category: string;
};
+ filterByCWE?: string;
hotspots: RawHotspot[];
hotspotsTotal: number;
loadingMore: boolean;
export default function HotspotSimpleList(props: HotspotSimpleListProps) {
const {
filterByCategory,
+ filterByCWE,
hotspots,
hotspotsTotal,
loadingMore,
standards
} = props;
+ const categoryLabel =
+ filterByCategory &&
+ SECURITY_STANDARD_RENDERER[filterByCategory.standard](standards, filterByCategory.category);
+
+ const cweLabel =
+ filterByCWE && SECURITY_STANDARD_RENDERER[SecurityStandard.CWE](standards, filterByCWE);
+
return (
<div className="hotspots-list-single-category huge-spacer-bottom">
<h1 className="hotspot-list-header bordered-bottom">
<div className="hotspot-category">
<div className="hotspot-category-header">
<strong className="flex-1 spacer-right break-word">
- {SECURITY_STANDARD_RENDERER[filterByCategory.standard](
- standards,
- filterByCategory.category
- )}
+ {categoryLabel}
+ {categoryLabel && cweLabel && <hr />}
+ {cweLabel}
</strong>
</div>
<ul>
import HotspotSimpleList, { HotspotSimpleListProps } from '../HotspotSimpleList';
it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot('filter by category');
+ expect(shallowRender({ filterByCategory: undefined, filterByCWE: '327' })).toMatchSnapshot(
+ 'filter by cwe'
+ );
+ expect(shallowRender({ filterByCWE: '327' })).toMatchSnapshot('filter by both');
});
function shallowRender(props: Partial<HotspotSimpleListProps> = {}) {
onLoadMore={jest.fn()}
selectedHotspot={hotspots[0]}
standards={{
- cwe: {},
+ cwe: { 327: { title: 'Use of a Broken or Risky Cryptographic Algorithm' } },
owaspTop10: {
a1: { title: 'A1 - SQL Injection' },
a3: { title: 'A3 - Sensitive Data Exposure' }
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly 1`] = `
+exports[`should render correctly: filter by both 1`] = `
<div
className="hotspots-list-single-category huge-spacer-bottom"
>
className="flex-1 spacer-right break-word"
>
A1 - A1 - SQL Injection
+ <hr />
+ CWE-327 - Use of a Broken or Risky Cryptographic Algorithm
+ </strong>
+ </div>
+ <ul>
+ <li
+ data-hotspot-key="h1"
+ key="h1"
+ >
+ <HotspotListItem
+ hotspot={
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ }
+ }
+ onClick={[MockFunction]}
+ selected={true}
+ />
+ </li>
+ <li
+ data-hotspot-key="h2"
+ key="h2"
+ >
+ <HotspotListItem
+ hotspot={
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h2",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ }
+ }
+ onClick={[MockFunction]}
+ selected={false}
+ />
+ </li>
+ </ul>
+ </div>
+ </div>
+ <ListFooter
+ count={2}
+ loadMore={[MockFunction]}
+ loading={false}
+ total={2}
+ />
+</div>
+`;
+
+exports[`should render correctly: filter by category 1`] = `
+<div
+ className="hotspots-list-single-category huge-spacer-bottom"
+>
+ <h1
+ className="hotspot-list-header bordered-bottom"
+ >
+ <SecurityHotspotIcon
+ className="spacer-right"
+ />
+ hotspots.list_title.2
+ </h1>
+ <div
+ className="big-spacer-bottom"
+ >
+ <div
+ className="hotspot-category"
+ >
+ <div
+ className="hotspot-category-header"
+ >
+ <strong
+ className="flex-1 spacer-right break-word"
+ >
+ A1 - A1 - SQL Injection
+ </strong>
+ </div>
+ <ul>
+ <li
+ data-hotspot-key="h1"
+ key="h1"
+ >
+ <HotspotListItem
+ hotspot={
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h1",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ }
+ }
+ onClick={[MockFunction]}
+ selected={true}
+ />
+ </li>
+ <li
+ data-hotspot-key="h2"
+ key="h2"
+ >
+ <HotspotListItem
+ hotspot={
+ Object {
+ "author": "Developer 1",
+ "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+ "creationDate": "2013-05-13T17:55:39+0200",
+ "key": "h2",
+ "line": 81,
+ "message": "'3' is a magic number.",
+ "project": "com.github.kevinsawicki:http-request",
+ "resolution": undefined,
+ "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+ "securityCategory": "command-injection",
+ "status": "TO_REVIEW",
+ "updateDate": "2013-05-13T17:55:39+0200",
+ "vulnerabilityProbability": "HIGH",
+ }
+ }
+ onClick={[MockFunction]}
+ selected={false}
+ />
+ </li>
+ </ul>
+ </div>
+ </div>
+ <ListFooter
+ count={2}
+ loadMore={[MockFunction]}
+ loading={false}
+ total={2}
+ />
+</div>
+`;
+
+exports[`should render correctly: filter by cwe 1`] = `
+<div
+ className="hotspots-list-single-category huge-spacer-bottom"
+>
+ <h1
+ className="hotspot-list-header bordered-bottom"
+ >
+ <SecurityHotspotIcon
+ className="spacer-right"
+ />
+ hotspots.list_title.2
+ </h1>
+ <div
+ className="big-spacer-bottom"
+ >
+ <div
+ className="hotspot-category"
+ >
+ <div
+ className="hotspot-category-header"
+ >
+ <strong
+ className="flex-1 spacer-right break-word"
+ >
+ CWE-327 - Use of a Broken or Risky Cryptographic Algorithm
</strong>
</div>
<ul>
export const SECURITY_STANDARDS = [
SecurityStandard.SONARSOURCE,
SecurityStandard.OWASP_TOP10,
- SecurityStandard.SANS_TOP25
+ SecurityStandard.SANS_TOP25,
+ SecurityStandard.CWE
];
export const SECURITY_STANDARD_RENDERER = {
...pick(query, [
SecurityStandard.SONARSOURCE,
SecurityStandard.OWASP_TOP10,
- SecurityStandard.SANS_TOP25
+ SecurityStandard.SANS_TOP25,
+ SecurityStandard.CWE
])
}
};