From c4d595c7825b16660f67bb5400b41cded1e0563e Mon Sep 17 00:00:00 2001 From: Andreea Lupu <58118008+Andreea-Lupu@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:12:31 +0200 Subject: [PATCH] patch: signature display redesign (#427) Signed-off-by: Raul-Cristian Kele Signed-off-by: Andreea-Lupu Co-authored-by: Raul-Cristian Kele --- src/__tests__/Explore/Explore.test.js | 101 ++++++++++-- src/__tests__/HomePage/Home.test.js | 45 ++++- src/__tests__/RepoPage/Repo.test.js | 26 +++ src/__tests__/TagPage/TagDetails.test.js | 29 +++- src/components/Explore/Explore.jsx | 1 - src/components/Home/Home.jsx | 1 - src/components/Repo/RepoDetails.jsx | 49 ++++-- src/components/Shared/PreviewCard.jsx | 5 +- src/components/Shared/RepoCard.jsx | 32 +++- src/components/Shared/SignatureTooltip.jsx | 20 +-- src/components/Tag/Tabs/DependsOn.jsx | 2 +- src/components/Tag/Tabs/IsDependentOn.jsx | 2 +- src/components/Tag/TagDetails.jsx | 30 +++- src/utilities/filterConstants.js | 7 +- src/utilities/objectModels.js | 4 +- .../vulnerabilityAndSignatureCheck.jsx | 44 +++-- .../vulnerabilityAndSignatureComponents.jsx | 156 ++++++++++++++---- tests/utils/test-data-parser.js | 2 +- 18 files changed, 443 insertions(+), 113 deletions(-) diff --git a/src/__tests__/Explore/Explore.test.js b/src/__tests__/Explore/Explore.test.js index b19c54b9..8568cd73 100644 --- a/src/__tests__/Explore/Explore.test.js +++ b/src/__tests__/Explore/Explore.test.js @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { api } from 'api'; import Explore from 'components/Explore/Explore'; @@ -38,7 +38,7 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: 'w', - IsSigned: false, + SignatureInfo: [], Licenses: '', Vendor: '', Labels: '', @@ -63,7 +63,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: false, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: false, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -88,7 +99,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -113,7 +135,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -138,7 +171,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -167,7 +211,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -192,7 +247,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: 'author1' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: 'author2' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -225,7 +291,7 @@ const filteredMockImageListWindows = () => { }; const filteredMockImageListSigned = () => { - const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.IsSigned); + const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.SignatureInfo?.length > 0); return { GlobalSearch: { Page: { TotalCount: 6, ItemCount: 6 }, @@ -273,7 +339,22 @@ describe('Explore component', () => { jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } }); render(); expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(1); - expect(await screen.findAllByTestId('verified-icon')).toHaveLength(6); + expect(await screen.findAllByTestId('untrusted-icon')).toHaveLength(2); + expect(await screen.findAllByTestId('verified-icon')).toHaveLength(10); + + const allUntrustedSignaturesIcons = await screen.findAllByTestId("untrusted-icon"); + fireEvent.mouseOver(allUntrustedSignaturesIcons[0]); + expect(await screen.findByText("Signed-by: Unknown")).toBeInTheDocument(); + const allTrustedSignaturesIcons = await screen.findAllByTestId("verified-icon"); + fireEvent.mouseOver(allTrustedSignaturesIcons[8]); + expect(await screen.findByText("Tool: cosign")).toBeInTheDocument(); + expect(await screen.findByText("Signed-by: author1")).toBeInTheDocument(); + fireEvent.mouseOver(allTrustedSignaturesIcons[9]); + expect(await screen.findByText("Tool: notation")).toBeInTheDocument(); + expect(await screen.findByText("Signed-by: author2")).toBeInTheDocument(); + const allNoSignedIcons = await screen.findAllByTestId("unverified-icon"); + fireEvent.mouseOver(allNoSignedIcons[0]); + expect(await screen.findByText("Not signed")).toBeInTheDocument(); }); it('renders vulnerability icons', async () => { diff --git a/src/__tests__/HomePage/Home.test.js b/src/__tests__/HomePage/Home.test.js index 0ef55a52..3a5073f0 100644 --- a/src/__tests__/HomePage/Home.test.js +++ b/src/__tests__/HomePage/Home.test.js @@ -32,7 +32,7 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: 'w', - IsSigned: false, + SignatureInfo: [], Licenses: '', Vendor: '', Labels: '', @@ -49,7 +49,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -66,7 +77,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -91,7 +113,7 @@ const mockImageListRecent = { NewestImage: { Tag: 'latest', Description: 'w', - IsSigned: false, + SignatureInfo: [], Licenses: '', Vendor: '', Labels: '', @@ -108,7 +130,18 @@ const mockImageListRecent = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -230,7 +263,7 @@ describe('Home component', () => { jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } }); render(); expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(4); - expect(await screen.findAllByTestId('verified-icon')).toHaveLength(5); + expect(await screen.findAllByTestId('verified-icon')).toHaveLength(10); }); it('renders vulnerability icons', async () => { diff --git a/src/__tests__/RepoPage/Repo.test.js b/src/__tests__/RepoPage/Repo.test.js index b95d357c..98f5b175 100644 --- a/src/__tests__/RepoPage/Repo.test.js +++ b/src/__tests__/RepoPage/Repo.test.js @@ -51,6 +51,18 @@ const mockRepoDetailsData = { NewestImage: { RepoName: 'mongo', IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: 'author1' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: 'author2' + } + ], Vulnerabilities: { MaxSeverity: 'CRITICAL', Count: 15 @@ -285,6 +297,20 @@ describe('Repo details component', () => { expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(1); }); + it('renders signature icons', async () => { + jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } }); + render(); + expect(await screen.findAllByTestId('verified-icon')).toHaveLength(2); + + const allTrustedSignaturesIcons = await screen.findAllByTestId("verified-icon"); + fireEvent.mouseOver(allTrustedSignaturesIcons[0]); + expect(await screen.findByText("Tool: cosign")).toBeInTheDocument(); + expect(await screen.findByText("Signed-by: author1")).toBeInTheDocument(); + fireEvent.mouseOver(allTrustedSignaturesIcons[1]); + expect(await screen.findByText("Tool: notation")).toBeInTheDocument(); + expect(await screen.findByText("Signed-by: author2")).toBeInTheDocument(); + }); + it("should log error if data can't be fetched", async () => { jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} }); const error = jest.spyOn(console, 'error').mockImplementation(() => {}); diff --git a/src/__tests__/TagPage/TagDetails.test.js b/src/__tests__/TagPage/TagDetails.test.js index d04cd1aa..e1143dd5 100644 --- a/src/__tests__/TagPage/TagDetails.test.js +++ b/src/__tests__/TagPage/TagDetails.test.js @@ -174,7 +174,20 @@ const mockImage = { MaxSeverity: 'CRITICAL', Count: 10 }, - Vendor: 'CentOS' + Vendor: 'CentOS', + IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: 'author1' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: 'author2' + } + ] } }; @@ -963,6 +976,20 @@ describe('Tags details', () => { expect(await screen.findByTestId('high-vulnerability-icon')).toBeInTheDocument(); }); + it('renders signature icons', async () => { + jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } }); + render(); + expect(await screen.findAllByTestId('verified-icon')).toHaveLength(2); + + const allTrustedSignaturesIcons = await screen.findAllByTestId("verified-icon"); + fireEvent.mouseOver(allTrustedSignaturesIcons[0]); + expect(await screen.findByText("Tool: cosign")).toBeInTheDocument(); + expect(await screen.findByText("Signed-by: author1")).toBeInTheDocument(); + fireEvent.mouseOver(allTrustedSignaturesIcons[1]); + expect(await screen.findByText("Tool: notation")).toBeInTheDocument(); + expect(await screen.findByText("Signed-by: author2")).toBeInTheDocument(); + }); + it('should copy the docker pull string to clipboard', async () => { jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } }); render(); diff --git a/src/components/Explore/Explore.jsx b/src/components/Explore/Explore.jsx index 8ccfaf5f..ccaf3931 100644 --- a/src/components/Explore/Explore.jsx +++ b/src/components/Explore/Explore.jsx @@ -221,7 +221,6 @@ function Explore({ searchInputValue }) { description={item.description} downloads={item.downloads} stars={item.stars} - isSigned={item.isSigned} signatureInfo={item.signatureInfo} isBookmarked={item.isBookmarked} isStarred={item.isStarred} diff --git a/src/components/Home/Home.jsx b/src/components/Home/Home.jsx index addd47d2..c36424d5 100644 --- a/src/components/Home/Home.jsx +++ b/src/components/Home/Home.jsx @@ -261,7 +261,6 @@ function Home() { description={item.description} downloads={item.downloads} stars={item.stars} - isSigned={item.isSigned} signatureInfo={item.signatureInfo} isBookmarked={item.isBookmarked} isStarred={item.isStarred} diff --git a/src/components/Repo/RepoDetails.jsx b/src/components/Repo/RepoDetails.jsx index f31d2dc2..d04b76b1 100644 --- a/src/components/Repo/RepoDetails.jsx +++ b/src/components/Repo/RepoDetails.jsx @@ -11,6 +11,7 @@ import { host } from '../../host'; import { useParams, useNavigate, createSearchParams } from 'react-router-dom'; import { mapToRepoFromRepoInfo } from 'utilities/objectModels'; import { isAuthenticated } from 'utilities/authUtilities'; +import filterConstants from 'utilities/filterConstants'; // components import { Card, CardContent, CardMedia, Chip, Grid, Stack, Tooltip, Typography, IconButton } from '@mui/material'; @@ -260,6 +261,28 @@ function RepoDetails() { return lastDate; }; + const getSignatureChips = () => { + const cosign = repoDetailData.signatureInfo + ?.map((s) => s.tool) + .includes(filterConstants.signatureToolConstants.COSIGN) + ? repoDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN) + : null; + const notation = repoDetailData.signatureInfo + ?.map((s) => s.tool) + .includes(filterConstants.signatureToolConstants.NOTATION) + ? repoDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION) + : null; + const sigArray = []; + if (cosign) sigArray.push(cosign); + if (notation) sigArray.push(notation); + if (sigArray.length === 0) return ; + return sigArray.map((sig, index) => ( +
+ +
+ )); + }; + return ( <> {isLoading ? ( @@ -288,10 +311,7 @@ function RepoDetails() { - + {getSignatureChips()} {isAuthenticated() && ( @@ -304,13 +324,20 @@ function RepoDetails() { )} {isAuthenticated() && ( - - {repoDetailData?.isBookmarked ? ( - - ) : ( - - )} - + + + {repoDetailData?.isBookmarked ? ( + + ) : ( + + )} + + )} diff --git a/src/components/Shared/PreviewCard.jsx b/src/components/Shared/PreviewCard.jsx index 919d91c2..380f3081 100644 --- a/src/components/Shared/PreviewCard.jsx +++ b/src/components/Shared/PreviewCard.jsx @@ -10,7 +10,7 @@ import repocube3 from '../../assets/repocube-3.png'; import repocube4 from '../../assets/repocube-4.png'; import { isEmpty } from 'lodash'; -import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; +import { VulnerabilityIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; // temporary utility to get image const randomIntFromInterval = (min, max) => { @@ -67,7 +67,7 @@ const useStyles = makeStyles(() => ({ function PreviewCard(props) { const classes = useStyles(); const navigate = useNavigate(); - const { name, isSigned, vulnerabilityData, logo } = props; + const { name, vulnerabilityData, logo } = props; const goToDetails = () => { navigate(`/image/${encodeURIComponent(name)}`); @@ -108,7 +108,6 @@ function PreviewCard(props) { - diff --git a/src/components/Shared/RepoCard.jsx b/src/components/Shared/RepoCard.jsx index 57188857..e196a092 100644 --- a/src/components/Shared/RepoCard.jsx +++ b/src/components/Shared/RepoCard.jsx @@ -32,15 +32,16 @@ import StarIcon from '@mui/icons-material/Star'; import StarBorderIcon from '@mui/icons-material/StarBorder'; import { useTheme } from '@emotion/react'; +import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; +import { Markdown } from 'utilities/MarkdowntojsxWrapper'; +import filterConstants from 'utilities/filterConstants'; + // placeholder images import repocube1 from '../../assets/repocube-1.png'; import repocube2 from '../../assets/repocube-2.png'; import repocube3 from '../../assets/repocube-3.png'; import repocube4 from '../../assets/repocube-4.png'; -import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; -import { Markdown } from 'utilities/MarkdowntojsxWrapper'; - // temporary utility to get image const randomIntFromInterval = (min, max) => { return Math.floor(Math.random() * (max - min + 1) + min); @@ -186,7 +187,6 @@ function RepoCard(props) { description, downloads, stars, - isSigned, signatureInfo, lastUpdated, version, @@ -296,6 +296,24 @@ function RepoCard(props) { ); }; + const getSignatureChips = () => { + const cosign = signatureInfo?.map((s) => s.tool).includes(filterConstants.signatureToolConstants.COSIGN) + ? signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN) + : null; + const notation = signatureInfo?.map((s) => s.tool).includes(filterConstants.signatureToolConstants.NOTATION) + ? signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION) + : null; + const sigArray = []; + if (cosign) sigArray.push(cosign); + if (notation) sigArray.push(notation); + if (sigArray.length === 0) return ; + return sigArray.map((sig, index) => ( +
+ +
+ )); + }; + return ( -
+
-
- -
+ {getSignatureChips()} diff --git a/src/components/Shared/SignatureTooltip.jsx b/src/components/Shared/SignatureTooltip.jsx index c4433b48..bde8ae17 100644 --- a/src/components/Shared/SignatureTooltip.jsx +++ b/src/components/Shared/SignatureTooltip.jsx @@ -1,19 +1,17 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Typography, Stack } from '@mui/material'; - import { isEmpty } from 'lodash'; +import { getStrongestSignature, getAllAuthorsOfSignatures } from 'utilities/vulnerabilityAndSignatureCheck'; -function SignatureTooltip({ isSigned, signatureInfo }) { - const { tool, isTrusted, author } = !isEmpty(signatureInfo) - ? signatureInfo[0] - : { tool: 'Unknown', isTrusted: 'Unknown', author: 'Unknown' }; +function SignatureTooltip({ signatureInfo }) { + const strongestSignature = useMemo(() => getStrongestSignature(signatureInfo)); - return ( + return isEmpty(strongestSignature) ? ( + Not signed + ) : ( - {isSigned ? 'Verified Signature' : 'Unverified Signature'} - Tool: {tool} - Trusted: {!isEmpty(isTrusted) ? isTrusted : 'Unknown'} - Author: {!isEmpty(author) ? author : 'Unknown'} + Tool: {strongestSignature?.tool || 'Unknown'} + Signed-by: {getAllAuthorsOfSignatures(signatureInfo) || 'Unknown'} ); } diff --git a/src/components/Tag/Tabs/DependsOn.jsx b/src/components/Tag/Tabs/DependsOn.jsx index 56b0ed51..1cfafb37 100644 --- a/src/components/Tag/Tabs/DependsOn.jsx +++ b/src/components/Tag/Tabs/DependsOn.jsx @@ -107,7 +107,7 @@ function DependsOn(props) { repoName={dependence.repoName} tag={dependence.tag} vendor={dependence.vendor} - isSigned={dependence.isSigned} + signatureInfo={dependence.signatureInfo} manifests={dependence.manifests} key={index} lastUpdated={dependence.lastUpdated} diff --git a/src/components/Tag/Tabs/IsDependentOn.jsx b/src/components/Tag/Tabs/IsDependentOn.jsx index 4afaf675..03ec2b3d 100644 --- a/src/components/Tag/Tabs/IsDependentOn.jsx +++ b/src/components/Tag/Tabs/IsDependentOn.jsx @@ -107,7 +107,7 @@ function IsDependentOn(props) { repoName={dependence.repoName} tag={dependence.tag} vendor={dependence.vendor} - isSigned={dependence.isSigned} + signatureInfo={dependence.signatureInfo} manifests={dependence.manifests} key={index} lastUpdated={dependence.lastUpdated} diff --git a/src/components/Tag/TagDetails.jsx b/src/components/Tag/TagDetails.jsx index 8242edc5..abe90d48 100644 --- a/src/components/Tag/TagDetails.jsx +++ b/src/components/Tag/TagDetails.jsx @@ -5,6 +5,7 @@ import React, { useEffect, useMemo, useState, useRef } from 'react'; import { api, endpoints } from '../../api'; import { host } from '../../host'; import { mapToImage } from '../../utilities/objectModels'; +import filterConstants from 'utilities/filterConstants'; import { isEmpty, head } from 'lodash'; // components @@ -224,6 +225,28 @@ function TagDetails() { } }; + const getSignatureChips = () => { + const cosign = imageDetailData.signatureInfo + ?.map((s) => s.tool) + .includes(filterConstants.signatureToolConstants.COSIGN) + ? imageDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN) + : null; + const notation = imageDetailData.signatureInfo + ?.map((s) => s.tool) + .includes(filterConstants.signatureToolConstants.NOTATION) + ? imageDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION) + : null; + const sigArray = []; + if (cosign) sigArray.push(cosign); + if (notation) sigArray.push(notation); + if (sigArray.length === 0) return ; + return sigArray.map((sig, index) => ( +
+ +
+ )); + }; + return ( <> {isLoading ? ( @@ -256,15 +279,12 @@ function TagDetails() {
- + - + {getSignatureChips()} diff --git a/src/utilities/filterConstants.js b/src/utilities/filterConstants.js index 8a95591f..25d0501e 100644 --- a/src/utilities/filterConstants.js +++ b/src/utilities/filterConstants.js @@ -69,6 +69,11 @@ const archFilters = [ } ]; -const filterConstants = { osFilters, imageFilters, archFilters }; +const signatureToolConstants = { + COSIGN: 'cosign', + NOTATION: 'notation' +}; + +const filterConstants = { osFilters, imageFilters, archFilters, signatureToolConstants }; export default filterConstants; diff --git a/src/utilities/objectModels.js b/src/utilities/objectModels.js index 03403858..c8c5d6bb 100644 --- a/src/utilities/objectModels.js +++ b/src/utilities/objectModels.js @@ -132,12 +132,12 @@ const mapSignatureInfo = (signatureInfo) => { return signatureInfo ? { tool: signatureInfo.Tool, - isTrusted: signatureInfo.IsTrusted?.toString(), + isTrusted: signatureInfo.IsTrusted, author: signatureInfo.Author } : { tool: 'Unknown', - isTrusted: 'Unknown', + isTrusted: false, author: 'Unknown' }; }; diff --git a/src/utilities/vulnerabilityAndSignatureCheck.jsx b/src/utilities/vulnerabilityAndSignatureCheck.jsx index 5f679199..dff55ef0 100644 --- a/src/utilities/vulnerabilityAndSignatureCheck.jsx +++ b/src/utilities/vulnerabilityAndSignatureCheck.jsx @@ -1,3 +1,4 @@ +import { isEmpty } from 'lodash'; import React from 'react'; import { NoneVulnerabilityIcon, @@ -12,14 +13,26 @@ import { CriticalVulnerabilityChip, UnverifiedSignatureIcon, VerifiedSignatureIcon, - UnverifiedSignatureChip, - VerifiedSignatureChip, UnknownVulnerabilityIcon, UnknownVulnerabilityChip, FailedScanIcon, - FailedScanChip + FailedScanChip, + NotTrustedSignatureIcon } from './vulnerabilityAndSignatureComponents'; +const getStrongestSignature = (signatureInfo) => { + if (isEmpty(signatureInfo)) return null; + const trusted = signatureInfo.find((si) => si.isTrusted); + if (!isEmpty(trusted)) return trusted; + return signatureInfo[0]; +}; + +const getAllAuthorsOfSignatures = (signatureInfo) => { + if (isEmpty(signatureInfo)) return ''; + const signatureAuthors = signatureInfo.filter((si) => si.isTrusted).map((si) => si.author); + return signatureAuthors.join(','); +}; + const VulnerabilityIconCheck = ({ vulnerabilitySeverity }) => { let result; let vulnerabilityStringTitle = ''; @@ -84,20 +97,17 @@ const VulnerabilityChipCheck = ({ vulnerabilitySeverity }) => { return result; }; -const SignatureIconCheck = ({ isSigned, signatureInfo }) => { - if (isSigned) { - return ; - } else { - return ; - } +const SignatureIconCheck = ({ signatureInfo }) => { + const strongestSignature = getStrongestSignature(signatureInfo); + if (strongestSignature === null) return ; + if (strongestSignature.isTrusted) return ; + return ; }; -const SignatureChipCheck = ({ isSigned }) => { - if (isSigned) { - return ; - } else { - return ; - } +export { + VulnerabilityIconCheck, + VulnerabilityChipCheck, + SignatureIconCheck, + getStrongestSignature, + getAllAuthorsOfSignatures }; - -export { VulnerabilityIconCheck, VulnerabilityChipCheck, SignatureIconCheck, SignatureChipCheck }; diff --git a/src/utilities/vulnerabilityAndSignatureComponents.jsx b/src/utilities/vulnerabilityAndSignatureComponents.jsx index 76e55177..1ec4e344 100644 --- a/src/utilities/vulnerabilityAndSignatureComponents.jsx +++ b/src/utilities/vulnerabilityAndSignatureComponents.jsx @@ -1,9 +1,10 @@ import React from 'react'; -import { Chip, Tooltip } from '@mui/material'; +import { Chip, Tooltip, Badge } from '@mui/material'; import SvgIcon from '@mui/material/SvgIcon'; import { ReactComponent as failedScanBug } from '../assets/failedScan.svg'; import { createSvgIcon } from '@mui/material/utils'; import SignatureTooltip from 'components/Shared/SignatureTooltip'; +import filterConstants from 'utilities/filterConstants'; const FilledBugIcon = createSvgIcon( , @@ -14,11 +15,15 @@ const OutlinedBugIcon = createSvgIcon( 'OutlinedBug' ); const UnverifiedShieldIcon = createSvgIcon( - , + , 'UnverifiedShield' ); -const VerifiedShieldIcon = createSvgIcon( - , +const CVerifiedShieldIcon = createSvgIcon( + , + 'VerifiedShield' +); +const NVerifiedShieldIcon = createSvgIcon( + , 'VerifiedShield' ); @@ -222,8 +227,9 @@ const CriticalVulnerabilityChip = () => { const UnverifiedSignatureIcon = ({ signatureInfo }) => { return ( - } placement="top"> + } placement="top"> { ); }; + +const NotTrustedSignatureIcon = ({ signatureInfo }) => { + return ( + } placement="top"> + {(signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.NOTATION && ( + + + + )) || + (signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.COSIGN && ( + + + + ))} + + ); +}; + const VerifiedSignatureIcon = ({ signatureInfo }) => { return ( - } placement="top"> - + } placement="top"> + {(signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.NOTATION && ( + + + + )) || + (signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.COSIGN && ( + + + + ))} ); }; @@ -270,19 +373,6 @@ const UnverifiedSignatureChip = () => { /> ); }; -const VerifiedSignatureChip = () => { - return ( - { - return; - }} - deleteIcon={} - /> - ); -}; export { NoneVulnerabilityIcon, @@ -298,9 +388,9 @@ export { HighVulnerabilityChip, CriticalVulnerabilityChip, UnverifiedSignatureIcon, + NotTrustedSignatureIcon, VerifiedSignatureIcon, UnverifiedSignatureChip, - VerifiedSignatureChip, FailedScanIcon, FailedScanChip }; diff --git a/tests/utils/test-data-parser.js b/tests/utils/test-data-parser.js index 24420ec4..fad0971e 100644 --- a/tests/utils/test-data-parser.js +++ b/tests/utils/test-data-parser.js @@ -149,7 +149,7 @@ const getRepoListOrderedAlpha = () => { // }; const getRepoCardNameForLocator = (repo) => { - return `${repo?.repo} ${repo?.tags[0]?.description?.slice(0, 10)}`; + return new RegExp(`${repo?.repo} \\d${repo?.tags[0]?.description?.slice(0, 10)}`); }; export {