Skip to content

Commit

Permalink
feat(cve): filter cves by severity
Browse files Browse the repository at this point in the history
Signed-off-by: Andreea-Lupu <[email protected]>
  • Loading branch information
Andreea-Lupu authored and rchincha committed Feb 15, 2024
1 parent c268991 commit e037c6c
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 19 deletions.
62 changes: 56 additions & 6 deletions src/__tests__/TagPage/VulnerabilitiesDetails.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,24 @@ const mockCVEListFiltered = {
}
};

const mockCVEListFilteredBySeverity = (severity) => {
return {
CVEListForImage: {
Tag: '',
Page: { ItemCount: 20, TotalCount: 20 },
Summary: {
Count: 5,
UnknownCount: 1,
LowCount: 1,
MediumCount: 1,
HighCount: 1,
CriticalCount: 1,
},
CVEList: mockCVEList.CVEListForImage.CVEList.filter((e) => e.Severity.includes(severity))
}
};
};

const mockCVEListFilteredExclude = {
CVEListForImage: {
Tag: '',
Expand Down Expand Up @@ -507,12 +525,6 @@ const mockCVEFixed = {
}
]
}
},
pageNotFixed: {
ImageListWithCVEFixed: {
Page: { TotalCount: 0, ItemCount: 0 },
Results: []
}
}
};

Expand Down Expand Up @@ -541,6 +553,44 @@ describe('Vulnerabilties page', () => {
await waitFor(() => expect(screen.getAllByText(/CVE/)).toHaveLength(20));
});

it('renders the vulnerabilities by severity', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
render(<StateVulnerabilitiesWrapper />);
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
await waitFor(() => expect(screen.getAllByText('Total 5')).toHaveLength(1));
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(20));
expect(screen.getByLabelText('Medium')).toBeInTheDocument();
const mediumSeverity = await screen.getByLabelText('Medium');
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('MEDIUM') } });
fireEvent.click(mediumSeverity);
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(6));
expect(screen.getByLabelText('High')).toBeInTheDocument();
const highSeverity = await screen.getByLabelText('High');
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('HIGH') } });
fireEvent.click(highSeverity);
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(1));
expect(screen.getByLabelText('Critical')).toBeInTheDocument();
const criticalSeverity = await screen.getByLabelText('Critical');
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('CRITICAL') } });
fireEvent.click(criticalSeverity);
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(1));
expect(screen.getByLabelText('Low')).toBeInTheDocument();
const lowSeverity = await screen.getByLabelText('Low');
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('LOW') } });
fireEvent.click(lowSeverity);
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(10));
expect(screen.getByLabelText('Unknown')).toBeInTheDocument();
const unknownSeverity = await screen.getByLabelText('Unknown');
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('UNKNOWN') } });
fireEvent.click(unknownSeverity);
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(1));
expect(screen.getByText('Total 5')).toBeInTheDocument();
const totalSeverity = await screen.getByText('Total 5');
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('') } });
fireEvent.click(totalSeverity);
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(20));
});

it('sends filtered query if user types in the search bar', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
render(<StateVulnerabilitiesWrapper />);
Expand Down
11 changes: 10 additions & 1 deletion src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,13 @@ const endpoints = {
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor IsDeletable } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
detailedImageInfo: (name, tag) =>
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Referrers {MediaType ArtifactType Size Digest Annotations{Key Value}} Tag Manifests {History {Layer {Size Digest} HistoryDescription {CreatedBy EmptyLayer}} Digest ConfigDigest LastUpdated Size Platform {Os Arch}} Vendor Licenses }}`,
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }, searchTerm = '', excludedTerm = '') => {
vulnerabilitiesForRepo: (
name,
{ pageNumber = 1, pageSize = 15 },
searchTerm = '',
excludedTerm = '',
severity = ''
) => {
let query = `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}", requestedPage: {limit:${pageSize} offset:${
(pageNumber - 1) * pageSize
}}`;
Expand All @@ -100,6 +106,9 @@ const endpoints = {
if (!isEmpty(excludedTerm)) {
query += `, excludedCVE: "${excludedTerm}"`;
}
if (!isEmpty(severity)) {
query += `, severity: "${severity}"`;
}
return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity Reference PackageList {Name InstalledVersion FixedVersion}} Summary {Count UnknownCount LowCount MediumCount HighCount CriticalCount}}}`;
},
allVulnerabilitiesForRepo: (name) =>
Expand Down
24 changes: 16 additions & 8 deletions src/components/Shared/VulnerabilityCountCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const lowBorderColor = '#f0ed94';
const unknownColor = '#f2ffdd';
const unknownBorderColor = '#e9f4d7';

const totalBorderColor = '#e0e5eb';

const fontSize = '0.75rem';

const useStyles = makeStyles((theme) => ({
Expand All @@ -30,7 +32,11 @@ const useStyles = makeStyles((theme) => ({
fontSize: fontSize,
fontWeight: '600',
borderRadius: '3px',
marginBottom: '0'
marginBottom: '0',
cursor: 'pointer'
},
totalSeverity: {
border: '1px solid ' + totalBorderColor
},
severityList: {
fontSize: fontSize,
Expand Down Expand Up @@ -63,25 +69,27 @@ const useStyles = makeStyles((theme) => ({

function VulnerabilitiyCountCard(props) {
const classes = useStyles();
const { total, critical, high, medium, low, unknown } = props;
const { total, critical, high, medium, low, unknown, filterBySeverity } = props;

return (
<Stack direction="row" spacing="0.5em">
<div className={[classes.cveCountCard].join(' ')}>Total {total}</div>
<Tooltip title="Total" onClick={() => filterBySeverity('')}>
<div className={[classes.cveCountCard, classes.totalSeverity].join(' ')}>Total {total}</div>
</Tooltip>
<div className={classes.severityList}>
<Tooltip title="Critical">
<Tooltip title="Critical" onClick={() => filterBySeverity('CRITICAL')}>
<div className={[classes.cveCountCard, classes.criticalSeverity].join(' ')}>C {critical}</div>
</Tooltip>
<Tooltip title="High">
<Tooltip title="High" onClick={() => filterBySeverity('HIGH')}>
<div className={[classes.cveCountCard, classes.highSeverity].join(' ')}>H {high}</div>
</Tooltip>
<Tooltip title="Medium">
<Tooltip title="Medium" onClick={() => filterBySeverity('MEDIUM')}>
<div className={[classes.cveCountCard, classes.mediumSeverity].join(' ')}>M {medium}</div>
</Tooltip>
<Tooltip title="Low">
<Tooltip title="Low" onClick={() => filterBySeverity('LOW')}>
<div className={[classes.cveCountCard, classes.lowSeverity].join(' ')}>L {low}</div>
</Tooltip>
<Tooltip title="Unknown">
<Tooltip title="Unknown" onClick={() => filterBySeverity('UNKNOWN')}>
<div className={[classes.cveCountCard, classes.unknownSeverity].join(' ')}>U {unknown}</div>
</Tooltip>
</div>
Expand Down
9 changes: 5 additions & 4 deletions src/components/Tag/Tabs/VulnerabilitiesDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ function VulnerabilitiesDetails(props) {
// pagination props
const [cveFilter, setCveFilter] = useState('');
const [cveExcludeFilter, setCveExcludeFilter] = useState('');
const [cveSeverityFilter, setCveSeverityFilter] = useState('');
const [pageNumber, setPageNumber] = useState(1);
const [isEndOfList, setIsEndOfList] = useState(false);
const listBottom = useRef(null);
Expand All @@ -178,7 +179,8 @@ function VulnerabilitiesDetails(props) {
getCVERequestName(),
{ pageNumber, pageSize: EXPLORE_PAGE_SIZE },
cveFilter,
cveExcludeFilter
cveExcludeFilter,
cveSeverityFilter
)}`,
abortController.signal
)
Expand Down Expand Up @@ -321,7 +323,7 @@ function VulnerabilitiesDetails(props) {
useEffect(() => {
if (isLoading) return;
resetPagination();
}, [cveFilter, cveExcludeFilter]);
}, [cveFilter, cveExcludeFilter, cveSeverityFilter]);

useEffect(() => {
return () => {
Expand Down Expand Up @@ -352,8 +354,6 @@ function VulnerabilitiesDetails(props) {
return;
}

console.log('Test');

return !isEmpty(cveSummary) ? (
<VulnerabilityCountCard
total={cveSummary.Count}
Expand All @@ -362,6 +362,7 @@ function VulnerabilitiesDetails(props) {
medium={cveSummary.MediumCount}
low={cveSummary.LowCount}
unknown={cveSummary.UnknownCount}
filterBySeverity={setCveSeverityFilter}
/>
) : (
<></>
Expand Down

0 comments on commit e037c6c

Please sign in to comment.