2020-03-09 03:52:25 +01:00
|
|
|
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
2022-05-13 10:00:45 +02:00
|
|
|
import { AdditionsService } from '../additions.service';
|
2020-02-24 08:42:18 +01:00
|
|
|
import {
|
2022-05-13 10:00:45 +02:00
|
|
|
ClrDatagridComparatorInterface,
|
|
|
|
ClrDatagridStateInterface,
|
|
|
|
ClrLoadingState,
|
|
|
|
} from '@clr/angular';
|
|
|
|
import { finalize } from 'rxjs/operators';
|
|
|
|
import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link';
|
2022-04-29 10:24:07 +02:00
|
|
|
import {
|
2022-05-13 10:00:45 +02:00
|
|
|
ProjectService,
|
|
|
|
ScannerVo,
|
|
|
|
ScanningResultService,
|
|
|
|
SystemInfoService,
|
|
|
|
UserPermissionService,
|
|
|
|
USERSTATICPERMISSION,
|
|
|
|
VulnerabilityItem,
|
|
|
|
} from '../../../../../../shared/services';
|
|
|
|
import { ErrorHandler } from '../../../../../../shared/units/error-handler';
|
|
|
|
import {
|
|
|
|
getPageSizeFromLocalStorage,
|
|
|
|
PageSizeMapKeys,
|
|
|
|
setPageSizeToLocalStorage,
|
|
|
|
SEVERITY_LEVEL_MAP,
|
|
|
|
VULNERABILITY_SEVERITY,
|
|
|
|
} from '../../../../../../shared/units/utils';
|
|
|
|
import { ResultBarChartComponent } from '../../vulnerability-scanning/result-bar-chart.component';
|
|
|
|
import { Subscription } from 'rxjs';
|
|
|
|
import { Artifact } from '../../../../../../../../ng-swagger-gen/models/artifact';
|
|
|
|
import { SessionService } from '../../../../../../shared/services/session.service';
|
|
|
|
import {
|
|
|
|
EventService,
|
|
|
|
HarborEvent,
|
|
|
|
} from '../../../../../../services/event-service/event.service';
|
2020-02-13 08:39:29 +01:00
|
|
|
|
|
|
|
@Component({
|
2022-05-13 10:00:45 +02:00
|
|
|
selector: 'hbr-artifact-vulnerabilities',
|
|
|
|
templateUrl: './artifact-vulnerabilities.component.html',
|
|
|
|
styleUrls: ['./artifact-vulnerabilities.component.scss'],
|
2020-02-13 08:39:29 +01:00
|
|
|
})
|
2020-03-09 03:52:25 +01:00
|
|
|
export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
2022-05-13 10:00:45 +02:00
|
|
|
@Input()
|
|
|
|
vulnerabilitiesLink: AdditionLink;
|
|
|
|
@Input()
|
|
|
|
projectName: string;
|
|
|
|
@Input()
|
|
|
|
projectId: number;
|
|
|
|
@Input()
|
|
|
|
repoName: string;
|
|
|
|
@Input()
|
|
|
|
digest: string;
|
|
|
|
@Input() artifact: Artifact;
|
|
|
|
scan_overview: any;
|
|
|
|
scanner: ScannerVo;
|
|
|
|
projectScanner: ScannerVo;
|
|
|
|
|
|
|
|
scanningResults: VulnerabilityItem[] = [];
|
|
|
|
loading: boolean = false;
|
|
|
|
hasEnabledScanner: boolean = false;
|
|
|
|
scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
|
|
|
severitySort: ClrDatagridComparatorInterface<VulnerabilityItem>;
|
|
|
|
cvssSort: ClrDatagridComparatorInterface<VulnerabilityItem>;
|
|
|
|
hasScanningPermission: boolean = false;
|
|
|
|
onSendingScanCommand: boolean = false;
|
|
|
|
onSendingStopCommand: boolean = false;
|
|
|
|
hasShowLoading: boolean = false;
|
|
|
|
@ViewChild(ResultBarChartComponent)
|
|
|
|
resultBarChartComponent: ResultBarChartComponent;
|
|
|
|
sub: Subscription;
|
|
|
|
hasViewInitWithDelay: boolean = false;
|
|
|
|
currentCVEList: Array<{ cve_id: string }> = [];
|
|
|
|
pageSize: number = getPageSizeFromLocalStorage(
|
|
|
|
PageSizeMapKeys.ARTIFACT_VUL_COMPONENT,
|
|
|
|
25
|
|
|
|
);
|
|
|
|
constructor(
|
|
|
|
private errorHandler: ErrorHandler,
|
|
|
|
private additionsService: AdditionsService,
|
|
|
|
private userPermissionService: UserPermissionService,
|
|
|
|
private scanningService: ScanningResultService,
|
|
|
|
private eventService: EventService,
|
|
|
|
private session: SessionService,
|
|
|
|
private projectService: ProjectService,
|
|
|
|
private systemInfoService: SystemInfoService
|
|
|
|
) {
|
|
|
|
const that = this;
|
|
|
|
this.severitySort = {
|
|
|
|
compare(a: VulnerabilityItem, b: VulnerabilityItem): number {
|
|
|
|
return that.getLevel(a) - that.getLevel(b);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
this.cvssSort = {
|
|
|
|
compare(a: VulnerabilityItem, b: VulnerabilityItem): number {
|
|
|
|
if (
|
|
|
|
a &&
|
|
|
|
a.preferred_cvss &&
|
|
|
|
a.preferred_cvss.score_v3 &&
|
|
|
|
b &&
|
|
|
|
b.preferred_cvss &&
|
|
|
|
b.preferred_cvss.score_v3
|
|
|
|
) {
|
|
|
|
return (
|
|
|
|
+a.preferred_cvss.score_v3 - +b.preferred_cvss.score_v3
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2020-02-13 08:39:29 +01:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
ngOnInit() {
|
|
|
|
this.getVulnerabilities();
|
|
|
|
this.getScanningPermission();
|
|
|
|
this.getProjectScanner();
|
|
|
|
if (!this.sub) {
|
|
|
|
this.sub = this.eventService.subscribe(
|
|
|
|
HarborEvent.UPDATE_VULNERABILITY_INFO,
|
|
|
|
(artifact: Artifact) => {
|
|
|
|
if (artifact?.digest === this.artifact?.digest) {
|
|
|
|
this.getVulnerabilities();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2021-01-07 10:16:52 +01:00
|
|
|
}
|
2022-05-13 10:00:45 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
this.hasViewInitWithDelay = true;
|
|
|
|
}, 0);
|
|
|
|
if (this.isSystemAdmin()) {
|
|
|
|
// get system and project CVE allow list
|
|
|
|
this.getCurrentCVEAllowList();
|
|
|
|
}
|
|
|
|
}
|
2020-02-13 08:39:29 +01:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
ngOnDestroy() {
|
|
|
|
if (this.sub) {
|
|
|
|
this.sub.unsubscribe();
|
|
|
|
this.sub = null;
|
2021-09-15 11:00:08 +02:00
|
|
|
}
|
2020-03-09 03:52:25 +01:00
|
|
|
}
|
2022-05-13 10:00:45 +02:00
|
|
|
|
|
|
|
getVulnerabilities() {
|
|
|
|
if (
|
|
|
|
this.vulnerabilitiesLink &&
|
|
|
|
!this.vulnerabilitiesLink.absolute &&
|
|
|
|
this.vulnerabilitiesLink.href
|
|
|
|
) {
|
|
|
|
if (!this.hasShowLoading) {
|
|
|
|
this.loading = true;
|
|
|
|
this.hasShowLoading = true;
|
|
|
|
}
|
|
|
|
this.additionsService
|
|
|
|
.getDetailByLink(this.vulnerabilitiesLink.href, true, false)
|
|
|
|
.pipe(
|
|
|
|
finalize(() => {
|
|
|
|
this.loading = false;
|
|
|
|
this.hasShowLoading = false;
|
|
|
|
})
|
|
|
|
)
|
|
|
|
.subscribe(
|
|
|
|
res => {
|
|
|
|
this.scan_overview = res;
|
|
|
|
if (
|
|
|
|
this.scan_overview &&
|
|
|
|
Object.values(this.scan_overview)[0]
|
|
|
|
) {
|
|
|
|
this.scanningResults =
|
|
|
|
(Object.values(this.scan_overview)[0] as any)
|
|
|
|
.vulnerabilities || [];
|
|
|
|
// sort
|
|
|
|
if (this.scanningResults) {
|
|
|
|
this.scanningResults.sort(
|
|
|
|
(a, b) =>
|
|
|
|
this.getLevel(b) - this.getLevel(a)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.scanner = (
|
|
|
|
Object.values(this.scan_overview)[0] as any
|
|
|
|
).scanner;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
this.errorHandler.error(error);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2020-10-23 09:03:33 +02:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
getScanningPermission(): void {
|
|
|
|
const permissions = [
|
|
|
|
{
|
|
|
|
resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY,
|
|
|
|
action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE
|
|
|
|
.CREATE,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
this.userPermissionService
|
|
|
|
.hasProjectPermissions(this.projectId, permissions)
|
|
|
|
.subscribe(
|
|
|
|
(results: Array<boolean>) => {
|
|
|
|
this.hasScanningPermission = results[0];
|
|
|
|
// only has label permission
|
|
|
|
},
|
|
|
|
error => this.errorHandler.error(error)
|
|
|
|
);
|
2020-03-09 03:52:25 +01:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
getProjectScanner(): void {
|
|
|
|
this.hasEnabledScanner = false;
|
|
|
|
this.scanBtnState = ClrLoadingState.LOADING;
|
|
|
|
this.scanningService.getProjectScanner(this.projectId).subscribe(
|
|
|
|
response => {
|
|
|
|
if (
|
|
|
|
response &&
|
|
|
|
'{}' !== JSON.stringify(response) &&
|
|
|
|
!response.disabled &&
|
|
|
|
response.health === 'healthy'
|
|
|
|
) {
|
|
|
|
this.scanBtnState = ClrLoadingState.SUCCESS;
|
|
|
|
this.hasEnabledScanner = true;
|
|
|
|
} else {
|
|
|
|
this.scanBtnState = ClrLoadingState.ERROR;
|
2021-10-13 10:45:16 +02:00
|
|
|
}
|
2022-05-13 10:00:45 +02:00
|
|
|
this.projectScanner = response;
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
this.scanBtnState = ClrLoadingState.ERROR;
|
|
|
|
}
|
|
|
|
);
|
2020-02-13 08:39:29 +01:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
getLevel(v: VulnerabilityItem): number {
|
|
|
|
if (v && v.severity && SEVERITY_LEVEL_MAP[v.severity]) {
|
|
|
|
return SEVERITY_LEVEL_MAP[v.severity];
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
refresh(): void {
|
|
|
|
this.getVulnerabilities();
|
2020-02-13 08:39:29 +01:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
severityText(severity: string): string {
|
|
|
|
switch (severity) {
|
|
|
|
case VULNERABILITY_SEVERITY.CRITICAL:
|
|
|
|
return 'VULNERABILITY.SEVERITY.CRITICAL';
|
|
|
|
case VULNERABILITY_SEVERITY.HIGH:
|
|
|
|
return 'VULNERABILITY.SEVERITY.HIGH';
|
|
|
|
case VULNERABILITY_SEVERITY.MEDIUM:
|
|
|
|
return 'VULNERABILITY.SEVERITY.MEDIUM';
|
|
|
|
case VULNERABILITY_SEVERITY.LOW:
|
|
|
|
return 'VULNERABILITY.SEVERITY.LOW';
|
|
|
|
case VULNERABILITY_SEVERITY.NONE:
|
|
|
|
return 'VULNERABILITY.SEVERITY.NONE';
|
|
|
|
default:
|
|
|
|
return 'UNKNOWN';
|
|
|
|
}
|
|
|
|
}
|
2020-02-13 08:39:29 +01:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
scanNow() {
|
|
|
|
this.onSendingScanCommand = true;
|
|
|
|
this.eventService.publish(
|
|
|
|
HarborEvent.START_SCAN_ARTIFACT,
|
|
|
|
this.repoName + '/' + this.digest
|
|
|
|
);
|
2020-02-13 08:39:29 +01:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
submitFinish(e: boolean) {
|
|
|
|
this.onSendingScanCommand = e;
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
submitStopFinish(e: boolean) {
|
|
|
|
this.onSendingStopCommand = e;
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
shouldShowBar(): boolean {
|
|
|
|
return (
|
|
|
|
this.hasViewInitWithDelay &&
|
|
|
|
this.resultBarChartComponent &&
|
|
|
|
(this.resultBarChartComponent.queued ||
|
|
|
|
this.resultBarChartComponent.scanning ||
|
|
|
|
this.resultBarChartComponent.error ||
|
|
|
|
this.resultBarChartComponent.stopped)
|
|
|
|
);
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
hasScanned(): boolean {
|
|
|
|
return (
|
|
|
|
this.hasViewInitWithDelay &&
|
|
|
|
this.resultBarChartComponent &&
|
|
|
|
!(
|
|
|
|
this.resultBarChartComponent.completed ||
|
|
|
|
this.resultBarChartComponent.error ||
|
|
|
|
this.resultBarChartComponent.queued ||
|
|
|
|
this.resultBarChartComponent.stopped ||
|
|
|
|
this.resultBarChartComponent.scanning
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
handleScanOverview(scanOverview: any): any {
|
|
|
|
if (scanOverview) {
|
|
|
|
return Object.values(scanOverview)[0];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
isSystemAdmin(): boolean {
|
|
|
|
const account = this.session.getCurrentUser();
|
|
|
|
return account && account.has_admin_role;
|
2020-04-07 11:13:40 +02:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
getCurrentCVEAllowList() {
|
|
|
|
this.projectService.getProject(this.projectId).subscribe(projectRes => {
|
|
|
|
if (
|
|
|
|
projectRes &&
|
|
|
|
projectRes.cve_allowlist &&
|
|
|
|
projectRes.metadata &&
|
|
|
|
projectRes.metadata.reuse_sys_cve_allowlist === 'false'
|
|
|
|
) {
|
|
|
|
// use project CVE allow list
|
|
|
|
this.currentCVEList = projectRes.cve_allowlist['items'];
|
|
|
|
} else {
|
|
|
|
// use system CVE allow list
|
|
|
|
this.systemInfoService
|
|
|
|
.getSystemAllowlist()
|
|
|
|
.subscribe(systemRes => {
|
|
|
|
if (
|
|
|
|
systemRes &&
|
|
|
|
systemRes.items &&
|
|
|
|
systemRes.items.length
|
|
|
|
) {
|
|
|
|
this.currentCVEList = systemRes.items;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
isInAllowList(CVEId: string): boolean {
|
|
|
|
if (this.currentCVEList && this.currentCVEList.length) {
|
|
|
|
for (let i = 0; i < this.currentCVEList.length; i++) {
|
|
|
|
if (CVEId === this.currentCVEList[i].cve_id) {
|
|
|
|
return true;
|
2020-10-23 09:03:33 +02:00
|
|
|
}
|
2022-05-13 10:00:45 +02:00
|
|
|
}
|
2020-10-23 09:03:33 +02:00
|
|
|
}
|
2022-05-13 10:00:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
getScannerInfo(scanner: ScannerVo): string {
|
|
|
|
if (scanner) {
|
|
|
|
if (scanner.name && scanner.version) {
|
|
|
|
return `${scanner.name}@${scanner.version}`;
|
|
|
|
}
|
|
|
|
if (scanner.name && !scanner.version) {
|
|
|
|
return `${scanner.name}`;
|
|
|
|
}
|
2020-10-23 09:03:33 +02:00
|
|
|
}
|
2022-05-13 10:00:45 +02:00
|
|
|
return '';
|
2020-10-23 09:03:33 +02:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
isRunningState(): boolean {
|
|
|
|
return (
|
|
|
|
this.hasViewInitWithDelay &&
|
|
|
|
this.resultBarChartComponent &&
|
|
|
|
(this.resultBarChartComponent.queued ||
|
|
|
|
this.resultBarChartComponent.scanning)
|
|
|
|
);
|
2021-01-07 10:16:52 +01:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
scanOrStop() {
|
|
|
|
if (this.isRunningState()) {
|
|
|
|
this.stopNow();
|
|
|
|
} else {
|
|
|
|
this.scanNow();
|
|
|
|
}
|
2021-09-15 11:00:08 +02:00
|
|
|
}
|
2021-10-13 10:45:16 +02:00
|
|
|
|
2022-05-13 10:00:45 +02:00
|
|
|
stopNow() {
|
|
|
|
this.onSendingStopCommand = true;
|
|
|
|
this.eventService.publish(
|
|
|
|
HarborEvent.STOP_SCAN_ARTIFACT,
|
|
|
|
this.repoName + '/' + this.digest
|
|
|
|
);
|
|
|
|
}
|
|
|
|
canScan(): boolean {
|
|
|
|
return (
|
|
|
|
this.hasEnabledScanner &&
|
|
|
|
this.hasScanningPermission &&
|
|
|
|
!this.onSendingScanCommand
|
|
|
|
);
|
|
|
|
}
|
|
|
|
load(state: ClrDatagridStateInterface) {
|
|
|
|
if (state?.page?.size) {
|
|
|
|
setPageSizeToLocalStorage(
|
|
|
|
PageSizeMapKeys.ARTIFACT_VUL_COMPONENT,
|
|
|
|
state.page.size
|
|
|
|
);
|
|
|
|
}
|
2022-04-29 10:24:07 +02:00
|
|
|
}
|
2020-02-13 08:39:29 +01:00
|
|
|
}
|