Display security scanner disabled badge (#1337)

Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
This commit is contained in:
Cynthia S. Garcia 2021-05-21 14:14:30 +02:00 committed by GitHub
parent 3452ca13d1
commit e57e554d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 183 additions and 65 deletions

View File

@ -3294,6 +3294,10 @@ components:
name:
type: string
nullable: false
whitelisted:
type: boolean
nullable: false
example: false
ts:
type: integer
nullable: false
@ -3406,10 +3410,6 @@ components:
official:
type: boolean
nullable: false
scanner_disabled:
type: boolean
nullable: false
example: false
ts:
type: integer
nullable: false
@ -3446,7 +3446,6 @@ components:
- last_tracking_ts
- last_scanning_ts
- disabled
- scanner_disabled
properties:
digest:
type: string
@ -3468,10 +3467,6 @@ components:
disabled:
type: boolean
nullable: false
scanner_disabled:
type: boolean
nullable: false
example: false
branch:
type: string
nullable: false
@ -3566,6 +3561,7 @@ components:
scanner_disabled:
type: boolean
nullable: false
example: false
user_alias:
type: string
nullable: false

View File

@ -16,6 +16,7 @@ import OrganizationInfo from './OrganizationInfo';
import styles from './PackageInfo.module.css';
import RepositoryIconLabel from './RepositoryIconLabel';
import RepositoryInfo from './RepositoryInfo';
import ScannerDisabledRepositoryBadge from './ScannerDisabledRepositoryBadge';
import SecurityRating from './SecutityRating';
import SignedBadge from './SignedBadge';
import StarBadge from './StarBadge';
@ -225,6 +226,9 @@ const PackageInfo = (props: Props) => {
onlyBadge={false}
withLink={buildPackageURL(props.package.normalizedName, props.package.repository, props.package.version!)}
/>
{props.package.repository.scannerDisabled && (
<ScannerDisabledRepositoryBadge scannerDisabled={props.package.repository.scannerDisabled} withTooltip />
)}
</div>
</>
);

View File

@ -1,17 +1,26 @@
import { isUndefined } from 'lodash';
import React from 'react';
import { FaEyeSlash } from 'react-icons/fa';
import ElementWithTooltip from './ElementWithTooltip';
import Label from './Label';
interface Props {
scannerDisabled: boolean;
className?: string;
withTooltip?: boolean;
}
const ScannerDisabledRepositoryBadge = (props: Props) => {
if (!props.scannerDisabled) return null;
return (
<Label text="Security scanner disabled" labelStyle="warning" className={props.className} icon={<FaEyeSlash />} />
<ElementWithTooltip
active
className={props.className}
element={<Label text="Security scanner disabled" labelStyle="warning" icon={<FaEyeSlash />} />}
tooltipMessage="Security scanning of this package has been disabled by the publisher."
visibleTooltip={!isUndefined(props.withTooltip) && props.withTooltip}
/>
);
};

View File

@ -45,3 +45,20 @@
font-size: 90%;
line-height: 1.75rem;
}
.badgesWrapper {
font-size: 80%;
}
.badgeDecorator {
top: -6px;
height: 14px;
width: 10px;
border-left: 1px solid var(--color-black-25);
border-bottom: 1px solid var(--color-black-25);
}
.badge {
border: 1px solid var(--orange);
color: var(--orange);
}

View File

@ -5,6 +5,7 @@ import { GoPackage } from 'react-icons/go';
import { ContainerImage } from '../../types';
import ButtonCopyToClipboard from '../common/ButtonCopyToClipboard';
import ElementWithTooltip from '../common/ElementWithTooltip';
import SeeAllModal from '../common/SeeAllModal';
import SmallTitle from '../common/SmallTitle';
import styles from './ContainersImages.module.css';
@ -20,6 +21,16 @@ interface ContainersList {
}
const ContainersImages = (props: Props) => {
const getBadge = (): JSX.Element => (
<ElementWithTooltip
className={styles.tooltipIcon}
element={<span className={`badge badge-pill my-1 ${styles.badge}`}>Whitelisted</span>}
tooltipMessage="This image has been whitelisted by the publisher and it wont be scanned for security vulnerabilities."
visibleTooltip
active
/>
);
const getAllContainers = useCallback((): ContainersList | null => {
if (isUndefined(props.containers) || isNull(props.containers) || props.containers.length === 0) return null;
@ -53,6 +64,14 @@ const ContainersImages = (props: Props) => {
</div>
{copyBtn}
</div>
{containerImage.whitelisted && (
<div className={`d-flex flex-column mb-1 ${styles.badgesWrapper}`}>
<div className="d-flex flex-row align-items-center">
<div className={`${styles.badgeDecorator} position-relative mx-1`} />
{getBadge()}
</div>
</div>
)}
</div>
);
@ -67,6 +86,7 @@ const ContainersImages = (props: Props) => {
{containerImage.name || containerImage.image}
</div>
{copyBtn}
{containerImage.whitelisted && <div className="ml-2 mr-1">{getBadge()}</div>}
</div>
</td>
</tr>

View File

@ -204,6 +204,7 @@ const Details = (props: Props) => {
})()}
<SecurityReport
disabledReport={props.package.repository.scannerDisabled}
summary={props.package.securityReportSummary}
packageId={props.package.packageId}
version={props.package.version!}
@ -211,6 +212,7 @@ const Details = (props: Props) => {
visibleSecurityReport={props.visibleSecurityReport}
searchUrlReferer={props.searchUrlReferer}
fromStarredPage={props.fromStarredPage}
containers={props.package.containersImages}
/>
<CapabilityLevel capabilityLevel={props.package.capabilities} />

View File

@ -22,6 +22,7 @@ interface Props {
visibleSecurityReport: boolean;
searchUrlReferer?: SearchFiltersURL;
fromStarredPage?: boolean;
hasWhitelistedContainers: boolean;
}
const SecurityModal = (props: Props) => {
@ -133,7 +134,11 @@ const SecurityModal = (props: Props) => {
>
<div className="m-3">
<div className="h5 mt-0 text-secondary text-uppercase font-weight-bold pb-2">Summary</div>
{props.totalVulnerabilities > 0 && <SummaryTable report={report} />}
{props.totalVulnerabilities > 0 && (
<>
<SummaryTable report={report} hasWhitelistedContainers={props.hasWhitelistedContainers} />
</>
)}
<SecuritySummary summary={props.summary} totalVulnerabilities={props.totalVulnerabilities} />

View File

@ -26,3 +26,12 @@
.badgeItems {
background-color: var(--color-black-5);
}
.legend {
font-size: 75%;
line-height: 1rem;
}
.disabledBadgeWrapper {
margin-top: calc(0.5rem + 4px);
}

View File

@ -17,6 +17,10 @@
width: auto;
}
.legend {
font-size: 75%;
}
@media only screen and (min-width: 1200px) {
.table {
font-size: 1rem;

View File

@ -9,6 +9,7 @@ import styles from './SummaryTable.module.css';
interface Props {
report: SecurityReport;
hasWhitelistedContainers: boolean;
}
const SummaryTable = (props: Props) => {
@ -65,6 +66,12 @@ const SummaryTable = (props: Props) => {
})}
</tbody>
</table>
{props.hasWhitelistedContainers && (
<div className={`text-muted ${styles.legend}`}>
* Some containers images used by this package have been whitelisted by the publisher, which may affect the
security rating.
</div>
)}
</div>
);
};

View File

@ -1,16 +1,19 @@
import { isEmpty, isNull, isUndefined } from 'lodash';
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { SearchFiltersURL, SecurityReportSummary, VulnerabilitySeverity } from '../../../types';
import { ContainerImage, SearchFiltersURL, SecurityReportSummary, VulnerabilitySeverity } from '../../../types';
import { SEVERITY_ORDER, SEVERITY_RATING } from '../../../utils/data';
import prettifyNumber from '../../../utils/prettifyNumber';
import sumObjectValues from '../../../utils/sumObjectValues';
import ScannerDisabledRepositoryBadge from '../../common/ScannerDisabledRepositoryBadge';
import SecurityRating from '../../common/SecutityRating';
import SmallTitle from '../../common/SmallTitle';
import SecurityModal from './Modal';
import styles from './SecurityReport.module.css';
interface Props {
disabledReport?: boolean;
containers?: ContainerImage[] | null;
className?: string;
summary?: SecurityReportSummary | null;
packageId: string;
@ -22,72 +25,113 @@ interface Props {
}
const SecurityReport = (props: Props) => {
if (isNull(props.summary) || isUndefined(props.summary) || isEmpty(props.summary)) return null;
const checkIfWhitelistedContainers = useCallback((): boolean => {
if (props.containers) {
return props.containers.some((container: ContainerImage) => container.whitelisted);
}
return false;
}, [props.containers]);
const total: number = sumObjectValues(props.summary);
const [total, setTotal] = useState(props.summary ? sumObjectValues(props.summary) : 0);
const [hasWhitelistedContainers, setHasWhitelistedContainers] = useState<boolean>(checkIfWhitelistedContainers());
useEffect(() => {
setTotal(props.summary ? sumObjectValues(props.summary) : 0);
}, [props.summary]);
useEffect(() => {
setHasWhitelistedContainers(checkIfWhitelistedContainers());
}, [checkIfWhitelistedContainers, props.containers]);
if (
(isNull(props.summary) || isUndefined(props.summary) || isEmpty(props.summary)) &&
isUndefined(props.disabledReport)
)
return null;
return (
<div className={props.className}>
<SmallTitle text="Security Report" />
<div className="mb-3">
{total === 0 ? (
<div className="d-flex flex-row align-items-center mb-2">
<div>
<small>No vulnerabilities found</small>
{props.disabledReport ? (
<div className={styles.disabledBadgeWrapper}>
<ScannerDisabledRepositoryBadge scannerDisabled />
<div className={`text-muted mt-2 ${styles.legend}`}>
Security scanning of this package has been disabled by the publisher.
</div>
<SecurityRating summary={props.summary} className="position-relative ml-2" onlyBadge />
</div>
) : (
<div className="d-flex flex-row align-items-center mb-2">
<div>
<small>
<span className="font-weight-bold mr-1">{prettifyNumber(total, 1)}</span>vulnerabilities found
</small>
</div>
<SecurityRating summary={props.summary} className="position-relative ml-1" onlyBadge />
</div>
)}
{total > 0 && (
<>
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => {
if (!props.summary!.hasOwnProperty(severity) || props.summary![severity] === 0) return null;
return (
<div
key={`summary_${severity}`}
data-testid="summaryItem"
className={`d-flex justify-content-between align-items-center pb-2 pb-md-0 pt-1 ${styles.summary}`}
>
<div className="d-flex flex-row align-items-center">
<span
data-testid="summaryBadge"
className={`badge position-relative mr-2 ${styles.badge}`}
style={{ backgroundColor: SEVERITY_RATING[severity]!.color }}
>
{' '}
</span>
<span className={`text-uppercase ${styles.title}`}>{severity}</span>
</div>
<span className={`badge badge-pill ${styles.badgeItems}`}>{props.summary![severity]}</span>
{total === 0 ? (
<div className="d-flex flex-row align-items-center mb-2">
<div>
<small>No vulnerabilities found</small>
</div>
);
})}
<SecurityRating summary={props.summary} className="position-relative ml-2" onlyBadge />
{hasWhitelistedContainers && <span className="font-weight-bold ml-1">*</span>}
</div>
) : (
<div className="d-flex flex-row align-items-center mb-2">
<div>
<small>
<span className="font-weight-bold mr-1">{prettifyNumber(total, 1)}</span>vulnerabilities found
</small>
</div>
<SecurityRating summary={props.summary} className="position-relative ml-1" onlyBadge />
{hasWhitelistedContainers && <span className="font-weight-bold ml-1">*</span>}
</div>
)}
{hasWhitelistedContainers && (
<div className={`text-muted mb-3 ${styles.legend}`}>
* Some containers images used by this package have been whitelisted by the publisher, which may affect
the security rating.
</div>
)}
{total > 0 && (
<>
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => {
if (!props.summary!.hasOwnProperty(severity) || props.summary![severity] === 0) return null;
return (
<div
key={`summary_${severity}`}
data-testid="summaryItem"
className={`d-flex justify-content-between align-items-center pb-2 pb-md-0 pt-1 ${styles.summary}`}
>
<div className="d-flex flex-row align-items-center">
<span
data-testid="summaryBadge"
className={`badge position-relative mr-2 ${styles.badge}`}
style={{ backgroundColor: SEVERITY_RATING[severity]!.color }}
>
{' '}
</span>
<span className={`text-uppercase ${styles.title}`}>{severity}</span>
</div>
<span className={`badge badge-pill ${styles.badgeItems}`}>{props.summary![severity]}</span>
</div>
);
})}
</>
)}
<div className="d-none d-md-block">
<SecurityModal
summary={props.summary!}
totalVulnerabilities={total}
packageId={props.packageId}
version={props.version}
createdAt={props.createdAt}
visibleSecurityReport={props.visibleSecurityReport}
searchUrlReferer={props.searchUrlReferer}
fromStarredPage={props.fromStarredPage}
hasWhitelistedContainers={hasWhitelistedContainers}
/>
</div>
</>
)}
<div className="d-none d-md-block">
<SecurityModal
summary={props.summary!}
totalVulnerabilities={total}
packageId={props.packageId}
version={props.version}
createdAt={props.createdAt}
visibleSecurityReport={props.visibleSecurityReport}
searchUrlReferer={props.searchUrlReferer}
fromStarredPage={props.fromStarredPage}
/>
</div>
</div>
</div>
);

View File

@ -108,6 +108,7 @@ export interface PackageStats {
export interface ContainerImage {
image: string;
name?: string;
whitelisted?: boolean;
}
export interface Version {