Skip to content

Commit

Permalink
feat: Add not vulnerable box to CVE detail header (#1784)
Browse files Browse the repository at this point in the history
* feat: Add not vulnerable box to CVE detail header

* Add not vulnerable system expandible section

* Update snapshots

* PR review fixes
  • Loading branch information
leSamo authored Oct 11, 2022
1 parent 2a97d00 commit ef85d5e
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 22 deletions.
5 changes: 4 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,10 @@
"notAvailable": "Not available",
"notDefined": "Not defined",
"notReviewed": "Not reviewed",
"notVulnerable.description": "{multiple, select, other {This system contains} true {These systems contain}} an affected package(s) with vulnerable code, but the functionality is disabled, not exposed, or mitigated, etc., and doesn't negatively impact the security of the system. Patching {multiple, select, other {this system} true {these systems}} in not needed immediately, but we recommend doing so eventually.",
"notVulnerable.box.description": "This system is affected by {cveId}, but deep threat analysis indicates it is not vulnerable.",
"notVulnerable.box.reason": "This system is not vulnerable because: {reason}",
"notVulnerable.box.title": "Affected but not vulnerable",
"notVulnerable.description": "{multiple, select, other {This system contains} true {These systems contain}} an affected package(s) with vulnerable code, but the functionality is disabled, not exposed, or mitigated, etc., and doesn't negatively impact the security of the system. Patching {multiple, select, other {this system} true {these systems}} is not needed immediately, but we recommend doing so eventually.",
"notVulnerable.label.title": "Not vulnerable",
"notificationBusinessRiskUpdateFailureBody": "Select items and update business risk to try again.",
"notificationBusinessRiskUpdateFailureTitle": "Couldn’t update business risk",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
TextContent,
TextVariants,
SplitItem,
Split,
Card,
CardBody,
ExpandableSection
} from '@patternfly/react-core';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { useIntl } from 'react-intl';
import messages from '../../../Messages';
import './CSAwRuleBox.scss';
import NotVulnerableLabel from '../Snippets/NotVulnerableLabel';
import { changeExposedSystemsParameters } from '../../../Store/Actions/Actions';
import { ONLY_NON_VULNERABLE_SYSTEMS } from '../../../Helpers/constants';

const NotVulnerableBox = ({ synopsis, notVulnerableSystemCount }) => {
const dispatch = useDispatch();
const intl = useIntl();

const handleExposedSystemFilter = () => {
dispatch(changeExposedSystemsParameters({ rule: ONLY_NON_VULNERABLE_SYSTEMS }));
};

return (
notVulnerableSystemCount > 0 &&
<Card className="card-box" ouiaId="not-vulnerable-box">
<ExpandableSection toggleText={
<Split>
<SplitItem className="pf-u-mr-xl">
<TextContent>
<Text component={TextVariants.h4}>
<NotVulnerableLabel className="pf-u-mr-sm" />
{intl.formatMessage(messages.notVulnerableBoxTitle)}
</Text>
</TextContent>
</SplitItem>
<SplitItem id="filter-affected-systems-split">
<TextContent>
<Text
id="filter-affected-systems"
onClick={event => {
handleExposedSystemFilter();
event.stopPropagation();
}}
component={TextVariants.small}
>
<Link
to={`/cves/${synopsis}`}
>
{
intl.formatMessage(
messages.filterByAffectedSystems,
{
count: notVulnerableSystemCount
}
)
}
</Link>
</Text>
</TextContent>
</SplitItem>
</Split>
}>
<CardBody className="rule-card-body">
{intl.formatMessage(messages.notVulnerableDescription, { multiple: true })}
</CardBody>
</ExpandableSection>
</Card>
);
};

NotVulnerableBox.propTypes = {
synopsis: PropTypes.string,
notVulnerableSystemCount: PropTypes.number
};

export default NotVulnerableBox;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CVEDetailsPageDescription from '../CVEDetailsPageDescription/CVEDetailsPa
import CVEDetailsPageSidebar from '../CVEDetailsPageSidebar/CVEDetailsPageSidebar';
import CSAwRuleBox from '../CSAwRuleBox/CSAwRuleBox';
import { KnownExploitSummary } from '../KnownExploitSummary/KnownExploitSummary';
import NotVulnerableBox from '../CSAwRuleBox/NotVulnerableBox';

const CVEDetailsPageSummary = ({
data,
Expand Down Expand Up @@ -36,6 +37,10 @@ const CVEDetailsPageSummary = ({
synopsis={data.data.synopsis}
rules={data.data.rules}
/>
<NotVulnerableBox
synopsis={data.data.synopsis}
notVulnerableSystemCount={data.data.affected_but_not_vulnerable}
/>
</Grid>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@ exports[`CVEDetailsPageSummary component should render with data 1`] = `
rules={Array []}
/>
</injectIntl(CSAwRuleBox)>
<NotVulnerableBox />
</div>
</Grid>
</CVEDetailsPageSummary>
Expand Down Expand Up @@ -2632,6 +2633,7 @@ exports[`CVEDetailsPageSummary component should render with enabled WithLoader 1
rules={Array []}
/>
</injectIntl(CSAwRuleBox)>
<NotVulnerableBox />
</div>
</Grid>
</CVEDetailsPageSummary>
Expand Down Expand Up @@ -3860,6 +3862,7 @@ exports[`CVEDetailsPageSummary component should render with long description 1`]
rules={Array []}
/>
</injectIntl(CSAwRuleBox)>
<NotVulnerableBox />
</div>
</Grid>
</CVEDetailsPageSummary>
Expand Down Expand Up @@ -5040,6 +5043,7 @@ exports[`CVEDetailsPageSummary component should render without data 1`] = `
rules={Array []}
/>
</injectIntl(CSAwRuleBox)>
<NotVulnerableBox />
</div>
</Grid>
</CVEDetailsPageSummary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ describe('SecurityRuleFilter', () => {
it('Should render the security option items', () => {
const options = [
{ label: 'Has security rule', value: 'true' },
{ label: 'Affected but not vulnerable', value: 'affected_not_vulnerable' },
{ label: 'Does not have security rule', value: 'false' }
]

Expand All @@ -22,6 +23,7 @@ describe('SecurityRuleFilter', () => {
]
const options = [
{ label: 'Has security rule', value: 'true' },
{ label: 'Affected but not vulnerable', value: 'affected_not_vulnerable' },
{ label: 'Does not have security rule', value: 'false' },
{ label: 'security rule 1', value: '1' },
{ label: 'security rule 2', value: '2' }
Expand Down Expand Up @@ -63,6 +65,7 @@ describe('SecurityRuleFilter', () => {

it('Should call apply with dynamic prams', () => {
const dropdownItems = [
{ label: 'Affected but not vulnerable', value: 'affected_not_vulnerable' },
{ label: 'Does not have security rule', value: 'false' }
]
const dynamic = [
Expand All @@ -72,6 +75,7 @@ describe('SecurityRuleFilter', () => {

]
const options = [
{ label: 'Affected but not vulnerable', value: 'affected_not_vulnerable' },
{ label: 'Does not have security rule', value: 'false' },
{ label: 'security rule 1', value: '1' },
{ label: 'security rule 2', value: '2' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,24 @@ export const InsightsNoSystemRule = ({ cve }) => {
InsightsNoSystemRule.propTypes = {
cve: PropType.string
};

export const InsightsNotVulnerable = ({ cve, mitigationReason }) => {
return (
<TextContent>
<Text component={TextVariants.h3}>
<FormattedMessage {...messages.notVulnerableBoxTitle} />
</Text>
<Text component={TextVariants.p} className="pf-u-mb-sm">
<FormattedMessage {...messages.notVulnerableBoxDescription} values={{ cveId: cve }} />
</Text>
<Text component={TextVariants.p}>
<FormattedMessage {...messages.notVulnerableBoxReason} values={{ reason: mitigationReason }} />
</Text>
</TextContent>
);
};

InsightsNotVulnerable.propTypes = {
cve: PropType.string,
mitigationReason: PropType.string
};
19 changes: 14 additions & 5 deletions src/Components/SmartComponents/CVEs/CVEsTableToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import {
removeFilters,
isFilterInDefaultState
} from '../../../Helpers/TableToolbarHelper';
import { CVES_DEFAULT_FILTERS, CVES_FILTER_PARAMS } from '../../../Helpers/constants';
import {
CVES_DEFAULT_FILTERS, CVES_FILTER_PARAMS,
ONLY_NON_VULNERABLE_SYSTEMS,
RULE_PRESENCE_OPTIONS
} from '../../../Helpers/constants';

const CVEsTableToolbarWithContext = ({ context, canEditStatusOrBusinessRisk, canExport, intl }) => {
const [exportPDF, setExportPDF] = useState(false);
Expand All @@ -42,7 +46,7 @@ const CVEsTableToolbarWithContext = ({ context, canEditStatusOrBusinessRisk, can
const { filter } = params;
const selectedCvesCount = selectedCves && selectedCves.length;

const selectOptions = selectAllCheckbox({
const selectOptions = selectAllCheckbox({
selectedItems: selectedCves,
selectorHandler: methods.selectCves,
items: cves,
Expand Down Expand Up @@ -112,12 +116,17 @@ const CVEsTableToolbarWithContext = ({ context, canEditStatusOrBusinessRisk, can
isDisabled: cves.meta.total_items === 0 && selectedCvesCount === 0,
checked: Boolean(selectedCvesCount),
ouiaId: 'bulk-select',
onSelect: ()=> selectOptions.handleOnCheckboxChange()
onSelect: () => selectOptions.handleOnCheckboxChange()
}}
filterConfig={{
items: [
useSearchFilter('filter', messages.cve, messages.searchFilterByCveID, filter, methods.apply),
securityRuleFilter(methods.apply, params),
securityRuleFilter(methods.apply, params, [],
{
isDynamic: false,
dropdownItems: RULE_PRESENCE_OPTIONS.filter(item => item.value !== ONLY_NON_VULNERABLE_SYSTEMS)
}
),
knownExploitFilter(methods.apply, params),
impactFilter(methods.apply, params),
useCvssBaseScoreFilter(methods.apply, params),
Expand Down Expand Up @@ -145,7 +154,7 @@ const CVEsTableToolbarWithContext = ({ context, canEditStatusOrBusinessRisk, can
isDisabled: cves.meta.total_items === 0
}}
/>
{ exportPDF &&
{exportPDF &&
<DownloadCVEsReport
showButton={false}
params={params}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
buildActiveFilters,
removeFilters
} from '../../../Helpers/TableToolbarHelper';
import { ANSIBLE_REMEDIATION } from '../../../Helpers/constants';
import { ANSIBLE_REMEDIATION, ONLY_NON_VULNERABLE_SYSTEMS, RULE_PRESENCE_OPTIONS } from '../../../Helpers/constants';

const SystemCveToolbarWithContext = ({
entity,
Expand Down Expand Up @@ -71,7 +71,11 @@ const SystemCveToolbarWithContext = ({

const allFilters = [
useSearchFilter('filter', messages.cve, messages.searchFilterByCveID, filter, methods.apply),
securityRuleFilter(methods.apply, parameters),
securityRuleFilter(methods.apply, parameters, [],
{
isDynamic: false,
dropdownItems: RULE_PRESENCE_OPTIONS.filter(item => item.value !== ONLY_NON_VULNERABLE_SYSTEMS)
}),
knownExploitFilter(methods.apply, parameters),
impactFilter(methods.apply, parameters),
useCvssBaseScoreFilter(methods.apply, parameters),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
fetchAffectedSystemsByCVE
} from '../../../Store/Actions/Actions';
import {
RULE_ABSENCE_OPTIONS, ANSIBLE_REMEDIATION
ANSIBLE_REMEDIATION, RULE_PRESENCE_OPTIONS
} from '../../../Helpers/constants';
import remediationFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/RemediationFilter';

Expand Down Expand Up @@ -124,7 +124,7 @@ export const SystemsExposedTableToolbar = ({
filterRuleValues,
{
isDynamic: true,
dropdownItems: RULE_ABSENCE_OPTIONS
dropdownItems: RULE_PRESENCE_OPTIONS.filter(item => item.value !== 'true')
}
),
statusFilter(apply, parameters),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ exports[`SystemsExpostedTableToolbar component Toolbar matches snapshot 1`] = `
Object {
"filterValues": Object {
"items": Array [
Object {
"label": "Affected but not vulnerable",
"value": "affected_not_vulnerable",
},
Object {
"label": "Does not have security rule",
"value": "false",
Expand Down
11 changes: 7 additions & 4 deletions src/Helpers/CVEHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { processDate } from '@redhat-cloud-services/frontend-components-utilitie
import React from 'react';
import { BUSINESS_RISK_OPTIONS, CUSTOMER_PORTAL_CVE_PATH, STATUS_OPTIONS } from './constants';
import { FormattedMessage } from 'react-intl';
import { InsightsSystemRule, InsightsNoSystemRule }
import { InsightsSystemRule, InsightsNoSystemRule, InsightsNotVulnerable }
from '../Components/PresentationalComponents/InsightsSystemRule/InsightsSystemRule';
import messages from '../Messages';

Expand All @@ -21,9 +21,11 @@ export const createExposedSystemsRows = ({ items: { data, meta }, cveId }) => {
...row.attributes,
patchAccess: meta.patch_access || false,
status: row.attributes.status_name,
children: row.attributes.rule
children: row.attributes?.rule?.rule?.reason
? <InsightsSystemRule cve={cveId} rule={row.attributes.rule} />
: <InsightsNoSystemRule cve={cveId} />
: row.attributes.mitigation_reason
? <InsightsNotVulnerable cve={cveId} mitigationReason={row.attributes.mitigation_reason} />
: <InsightsNoSystemRule cve={cveId} />
}));

};
Expand Down Expand Up @@ -89,7 +91,8 @@ export function createCveDetailsPage(cves) {
systems_status_divergent: data.attributes.systems_status_divergent,
rules: data.attributes.rules,
celebrity_name: data.attributes.celebrity_name,
known_exploit: data.attributes.known_exploit
known_exploit: data.attributes.known_exploit,
affected_but_not_vulnerable: data.attributes.affected_but_not_vulnerable
};

return { data, meta, isLoading };
Expand Down
4 changes: 2 additions & 2 deletions src/Helpers/TableToolbarHelper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import messages from '../Messages';
import { FILTERS } from './constants';
import { FILTERS, ONLY_NON_VULNERABLE_SYSTEMS } from './constants';
import { intl } from '../Utilities/IntlProvider';
import isEqual from 'lodash/isEqual';

Expand All @@ -23,7 +23,7 @@ export const buildActiveFilters = (currentFilters, filterRuleValues = []) => {
else if (key === 'rule_presence') {
const filteredRule = filterRuleValues.find(({ value }) => value === parameter);

['true', 'false'].includes(parameter)
['true', 'false', ONLY_NON_VULNERABLE_SYSTEMS].includes(parameter)
? object.name = FILTERS.rule_presence.items.find(e => e.value === parameter).label
: object.name = filteredRule && filteredRule.label || parameter;

Expand Down
19 changes: 14 additions & 5 deletions src/Helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,23 @@ export const NotAuthorizedNotification = {
title: intl.formatMessage(messages.notAuthorizedNotificationTitle)
};

export const ONLY_NON_VULNERABLE_SYSTEMS = 'affected_not_vulnerable';

export const RULE_PRESENCE_OPTIONS = [
{ value: 'true', label: intl.formatMessage(messages.withSecurityRule) },
{ value: 'false', label: intl.formatMessage(messages.withoutSecurityRule) }
{
value: 'true',
label: intl.formatMessage(messages.withSecurityRule)
},
{
value: ONLY_NON_VULNERABLE_SYSTEMS,
label: intl.formatMessage(messages.notVulnerableBoxTitle)
},
{
value: 'false',
label: intl.formatMessage(messages.withoutSecurityRule)
}
];

// exposed table can filter out only without rules systems
export const RULE_ABSENCE_OPTIONS = [RULE_PRESENCE_OPTIONS[1]];

// NOTE value is passed as string cause there is a bug in ConditionalFilter
// when you pass boolean (true, false) onChange returns 1 instead of false
export const KNOWN_EXPLOIT_FILTER_OPTIONS = [
Expand Down
Loading

0 comments on commit ef85d5e

Please sign in to comment.