diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 5f0457790..c9e1e8a50 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1192,6 +1192,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/stop: @@ -1223,6 +1225,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/{report_id}/log: @@ -1476,6 +1480,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/labels: @@ -4823,6 +4829,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /schedules: @@ -6456,6 +6464,14 @@ responses: type: string schema: $ref: '#/definitions/Errors' + '422': + description: Unsupported Type + headers: + X-Request-Id: + description: The ID of the corresponding request for the response + type: string + schema: + $ref: '#/definitions/Errors' '500': description: Internal server error headers: @@ -6800,6 +6816,8 @@ definitions: format: int64 description: 'Time in seconds required to create the report' example: 300 + scanner: + $ref: '#/definitions/Scanner' NativeReportSummary: type: object description: 'The summary for the native report' diff --git a/src/controller/event/handler/internal/artifact.go b/src/controller/event/handler/internal/artifact.go index 9218db95a..0e9b3f5bb 100644 --- a/src/controller/event/handler/internal/artifact.go +++ b/src/controller/event/handler/internal/artifact.go @@ -24,6 +24,7 @@ import ( "time" "github.com/goharbor/harbor/src/controller/artifact" + "github.com/goharbor/harbor/src/controller/artifact/processor/sbom" "github.com/goharbor/harbor/src/controller/event" "github.com/goharbor/harbor/src/controller/event/operator" "github.com/goharbor/harbor/src/controller/repository" @@ -36,6 +37,7 @@ import ( "github.com/goharbor/harbor/src/pkg" pkgArt "github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/scan/report" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/task" ) @@ -319,6 +321,11 @@ func (a *ArtifactEventHandler) onDelete(ctx context.Context, event *event.Artifa log.Errorf("failed to delete scan reports of artifact %v, error: %v", unrefDigests, err) } + if event.Artifact.Type == sbom.ArtifactTypeSBOM && len(event.Artifact.Digest) > 0 { + if err := reportMgr.DeleteByExtraAttr(ctx, v1.MimeTypeSBOMReport, "sbom_digest", event.Artifact.Digest); err != nil { + log.Errorf("failed to delete scan reports of with sbom digest %v, error: %v", event.Artifact.Digest, err) + } + } return nil } diff --git a/src/controller/event/topic.go b/src/controller/event/topic.go index d099a8dbb..5898bf4af 100644 --- a/src/controller/event/topic.go +++ b/src/controller/event/topic.go @@ -159,7 +159,7 @@ func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { ResourceType: "artifact"} if len(p.Tags) == 0 { - auditLog.Resource = fmt.Sprintf("%s:%s", + auditLog.Resource = fmt.Sprintf("%s@%s", p.Artifact.RepositoryName, p.Artifact.Digest) } else { auditLog.Resource = fmt.Sprintf("%s:%s", @@ -222,7 +222,7 @@ func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { Operation: rbac.ActionDelete.String(), Username: d.Operator, ResourceType: "artifact", - Resource: fmt.Sprintf("%s:%s", d.Artifact.RepositoryName, d.Artifact.Digest)} + Resource: fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)} return auditLog, nil } diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index c1b68947c..c70221a0f 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -751,13 +751,34 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, reportContent := reports[0].Report result := map[string]interface{}{} if len(reportContent) == 0 { - log.Warning("no content for current report") + status := bc.retrieveStatusFromTask(ctx, reports[0].UUID) + if len(status) > 0 { + result[sbomModel.ReportID] = reports[0].UUID + result[sbomModel.ScanStatus] = status + } + log.Debug("no content for current report") return result, nil } err = json.Unmarshal([]byte(reportContent), &result) return result, err } +// retrieve the status from task +func (bc *basicController) retrieveStatusFromTask(ctx context.Context, reportID string) string { + if len(reportID) == 0 { + return "" + } + tasks, err := bc.taskMgr.ListScanTasksByReportUUID(ctx, reportID) + if err != nil { + log.Warningf("can not find the task with report UUID %v, error %v", reportID, err) + return "" + } + if len(tasks) > 0 { + return tasks[0].Status + } + return "" +} + // GetScanLog ... func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) { if len(uuid) == 0 { diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 521325d79..19a559e0e 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -70,9 +70,10 @@ type ControllerTestSuite struct { tagCtl *tagtesting.FakeController - registration *scanner.Registration - artifact *artifact.Artifact - rawReport string + registration *scanner.Registration + artifact *artifact.Artifact + wrongArtifact *artifact.Artifact + rawReport string execMgr *tasktesting.ExecutionManager taskMgr *tasktesting.Manager @@ -101,6 +102,9 @@ func (suite *ControllerTestSuite) SetupSuite() { suite.artifact.Digest = "digest-code" suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact + suite.wrongArtifact = &artifact.Artifact{Artifact: art.Artifact{ID: 2, ProjectID: 1}} + suite.wrongArtifact.Digest = "digest-wrong" + m := &v1.ScannerAdapterMetadata{ Scanner: &v1.Scanner{ Name: "Trivy", @@ -202,8 +206,11 @@ func (suite *ControllerTestSuite) SetupSuite() { Report: `{"sbom_digest": "sha256:1234567890", "scan_status": "Success", "duration": 3, "start_time": "2021-09-01T00:00:00Z", "end_time": "2021-09-01T00:00:03Z"}`, }, } + + emptySBOMReport := []*scan.Report{{Report: ``, UUID: "rp-uuid-004"}} mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeNativeReport}).Return(reports, nil) mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil) + mgr.On("GetBy", mock.Anything, suite.wrongArtifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(emptySBOMReport, nil) mgr.On("Get", mock.Anything, "rp-uuid-001").Return(reports[0], nil) mgr.On("UpdateReportData", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil) mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil) @@ -654,6 +661,12 @@ func (suite *ControllerTestSuite) TestGenerateSBOMSummary() { suite.NotNil(dgst) suite.Equal("Success", status) suite.Equal("sha256:1234567890", dgst) + tasks := []*task.Task{{Status: "Error"}} + suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once() + sum2, err := suite.c.GetSummary(context.TODO(), suite.wrongArtifact, []string{v1.MimeTypeSBOMReport}) + suite.Nil(err) + suite.NotNil(sum2) + } func TestIsSBOMMimeTypes(t *testing.T) { @@ -683,5 +696,11 @@ func (suite *ControllerTestSuite) TestDeleteArtifactAccessories() { } ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) suite.NoError(suite.c.deleteArtifactAccessories(ctx, reports)) - +} + +func (suite *ControllerTestSuite) TestRetrieveStatusFromTask() { + tasks := []*task.Task{{Status: "Error"}} + suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once() + status := suite.c.retrieveStatusFromTask(nil, "rp-uuid-004") + suite.Equal("Error", status) } diff --git a/src/pkg/scan/dao/scan/report.go b/src/pkg/scan/dao/scan/report.go index f30b36ddb..8da0e67af 100644 --- a/src/pkg/scan/dao/scan/report.go +++ b/src/pkg/scan/dao/scan/report.go @@ -16,6 +16,7 @@ package scan import ( "context" + "fmt" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/orm" @@ -38,6 +39,8 @@ type DAO interface { UpdateReportData(ctx context.Context, uuid string, report string) error // Update update report Update(ctx context.Context, r *Report, cols ...string) error + // DeleteByExtraAttr delete the scan_report by mimeType and extra attribute + DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error } // New returns an instance of the default DAO @@ -110,3 +113,14 @@ func (d *dao) Update(ctx context.Context, r *Report, cols ...string) error { } return nil } + +func (d *dao) DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error { + o, err := orm.FromContext(ctx) + if err != nil { + return err + } + delReportSQL := "delete from scan_report where mime_type = ? and report::jsonb @> ?" + dgstJSONStr := fmt.Sprintf(`{"%s":"%s"}`, attrName, attrValue) + _, err = o.Raw(delReportSQL, mimeType, dgstJSONStr).Exec() + return err +} diff --git a/src/pkg/scan/dao/scan/report_test.go b/src/pkg/scan/dao/scan/report_test.go index ccda8e02b..ab4662289 100644 --- a/src/pkg/scan/dao/scan/report_test.go +++ b/src/pkg/scan/dao/scan/report_test.go @@ -53,14 +53,23 @@ func (suite *ReportTestSuite) SetupTest() { RegistrationUUID: "ruuid", MimeType: v1.MimeTypeNativeReport, } - suite.create(r) + sbomReport := &Report{ + UUID: "uuid3", + Digest: "digest1003", + RegistrationUUID: "ruuid", + MimeType: v1.MimeTypeSBOMReport, + Report: `{"sbom_digest": "sha256:abc"}`, + } + suite.create(sbomReport) } // TearDownTest clears enf for test case. func (suite *ReportTestSuite) TearDownTest() { _, err := suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid"}}) require.NoError(suite.T(), err) + _, err = suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid3"}}) + require.NoError(suite.T(), err) } // TestReportList tests list reports with query parameters. @@ -95,7 +104,7 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() { err := suite.dao.UpdateReportData(orm.Context(), "uuid", "{}") suite.Require().NoError(err) - l, err := suite.dao.List(orm.Context(), nil) + l, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"uuid": "uuid"})) suite.Require().NoError(err) suite.Require().Equal(1, len(l)) suite.Equal("{}", l[0].Report) @@ -104,6 +113,17 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() { suite.Require().NoError(err) } +func (suite *ReportTestSuite) TestDeleteReportBySBOMDigest() { + l, err := suite.dao.List(orm.Context(), nil) + suite.Require().NoError(err) + suite.Equal(2, len(l)) + err = suite.dao.DeleteByExtraAttr(orm.Context(), v1.MimeTypeSBOMReport, "sbom_digest", "sha256:abc") + suite.Require().NoError(err) + l2, err := suite.dao.List(orm.Context(), nil) + suite.Require().NoError(err) + suite.Equal(1, len(l2)) +} + func (suite *ReportTestSuite) create(r *Report) { id, err := suite.dao.Create(orm.Context(), r) suite.Require().NoError(err) diff --git a/src/pkg/scan/report/manager.go b/src/pkg/scan/report/manager.go index fa6415ed0..3bc2de1f1 100644 --- a/src/pkg/scan/report/manager.go +++ b/src/pkg/scan/report/manager.go @@ -104,6 +104,8 @@ type Manager interface { // Update update report information Update(ctx context.Context, r *scan.Report, cols ...string) error + // DeleteByExtraAttr delete scan_report by sbom_digest + DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error } // basicManager is a default implementation of report manager. @@ -226,3 +228,7 @@ func (bm *basicManager) List(ctx context.Context, query *q.Query) ([]*scan.Repor func (bm *basicManager) Update(ctx context.Context, r *scan.Report, cols ...string) error { return bm.dao.Update(ctx, r, cols...) } + +func (bm *basicManager) DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error { + return bm.dao.DeleteByExtraAttr(ctx, mimeType, attrName, attrValue) +} diff --git a/src/pkg/scan/sbom/model/summary.go b/src/pkg/scan/sbom/model/summary.go index 46c870f97..0d7e6a2ef 100644 --- a/src/pkg/scan/sbom/model/summary.go +++ b/src/pkg/scan/sbom/model/summary.go @@ -27,6 +27,10 @@ const ( Duration = "duration" // ScanStatus ... ScanStatus = "scan_status" + // ReportID ... + ReportID = "report_id" + // Scanner ... + Scanner = "scanner" ) // Summary includes the sbom summary information diff --git a/src/pkg/scan/sbom/sbom.go b/src/pkg/scan/sbom/sbom.go index bbf405571..f8e6d2e43 100644 --- a/src/pkg/scan/sbom/sbom.go +++ b/src/pkg/scan/sbom/sbom.go @@ -87,7 +87,7 @@ func (v *scanHandler) RequiredPermissions() []*types.Policy { // PostScan defines task specific operations after the scan is complete func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) { - sbomContent, err := retrieveSBOMContent(rawReport) + sbomContent, s, err := retrieveSBOMContent(rawReport) if err != nil { return "", err } @@ -107,19 +107,21 @@ func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel myLogger.Errorf("error when create accessory from image %v", err) return "", err } - return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success") + return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success", s) } // annotations defines the annotations for the accessory artifact func (v *scanHandler) annotations() map[string]string { + t := time.Now().Format(time.RFC3339) return map[string]string{ - "created-by": "Harbor", - "org.opencontainers.artifact.created": time.Now().Format(time.RFC3339), + "created": t, + "created-by": "Harbor", + "org.opencontainers.artifact.created": t, "org.opencontainers.artifact.description": "SPDX JSON SBOM", } } -func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string) (string, error) { +func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string, scanner *v1.Scanner) (string, error) { summary := sbom.Summary{} endTime := time.Now() summary[sbom.StartTime] = startTime @@ -128,6 +130,7 @@ func (v *scanHandler) generateReport(startTime time.Time, repository, digest, st summary[sbom.SBOMRepository] = repository summary[sbom.SBOMDigest] = digest summary[sbom.ScanStatus] = status + summary[sbom.Scanner] = scanner rep, err := json.Marshal(summary) if err != nil { return "", err @@ -148,15 +151,15 @@ func registryFQDN(ctx context.Context) string { } // retrieveSBOMContent retrieves the "sbom" field from the raw report -func retrieveSBOMContent(rawReport string) ([]byte, error) { +func retrieveSBOMContent(rawReport string) ([]byte, *v1.Scanner, error) { rpt := vuln.Report{} err := json.Unmarshal([]byte(rawReport), &rpt) if err != nil { - return nil, err + return nil, nil, err } sbomContent, err := json.Marshal(rpt.SBOM) if err != nil { - return nil, err + return nil, nil, err } - return sbomContent, nil + return sbomContent, rpt.Scanner, nil } diff --git a/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts b/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts index f693753ad..6e3b4097c 100644 --- a/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts +++ b/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts @@ -78,6 +78,7 @@ export const ACTION_RESOURCE_I18N_MAP = { log: 'ROBOT_ACCOUNT.LOG', 'notification-policy': 'ROBOT_ACCOUNT.NOTIFICATION_POLICY', quota: 'ROBOT_ACCOUNT.QUOTA', + sbom: 'ROBOT_ACCOUNT.SBOM', }; export function convertKey(key: string) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html index 99208d3f4..1a71ebf3a 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html @@ -13,10 +13,13 @@ [clrIfActive]="currentTabLinkId === 'vulnerability'"> + @@ -50,6 +55,7 @@ [clrIfActive]="currentTabLinkId === 'build-history'"> @@ -67,6 +73,7 @@ [clrIfActive]="currentTabLinkId === 'summary-link'"> @@ -81,6 +88,7 @@ @@ -97,6 +105,7 @@ diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts index 0f8a801e4..c4147cb57 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts @@ -4,6 +4,8 @@ import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/additi import { CURRENT_BASE_HREF } from '../../../../../shared/units/utils'; import { SharedTestingModule } from '../../../../../shared/shared.module'; import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ArtifactListPageService } from '../artifact-list-page/artifact-list-page.service'; +import { ClrLoadingState } from '@clr/angular'; describe('ArtifactAdditionsComponent', () => { const mockedAdditionLinks: AdditionLinks = { @@ -12,6 +14,18 @@ describe('ArtifactAdditionsComponent', () => { href: CURRENT_BASE_HREF + '/test', }, }; + const mockedArtifactListPageService = { + hasScannerSupportSBOM(): boolean { + return true; + }, + hasEnabledScanner(): boolean { + return true; + }, + getScanBtnState(): ClrLoadingState { + return ClrLoadingState.SUCCESS; + }, + init() {}, + }; let component: ArtifactAdditionsComponent; let fixture: ComponentFixture; @@ -20,6 +34,12 @@ describe('ArtifactAdditionsComponent', () => { imports: [SharedTestingModule], declarations: [ArtifactAdditionsComponent], schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: ArtifactListPageService, + useValue: mockedArtifactListPageService, + }, + ], }).compileComponents(); }); @@ -27,6 +47,7 @@ describe('ArtifactAdditionsComponent', () => { fixture = TestBed.createComponent(ArtifactAdditionsComponent); component = fixture.componentInstance; component.additionLinks = mockedAdditionLinks; + component.tab = 'vulnerability'; fixture.detectChanges(); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts index 45994ac8e..a0f5007b8 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts @@ -10,7 +10,8 @@ import { ADDITIONS } from './models'; import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/addition-links'; import { AdditionLink } from '../../../../../../../ng-swagger-gen/models/addition-link'; import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact'; -import { ClrTabs } from '@clr/angular'; +import { ClrLoadingState, ClrTabs } from '@clr/angular'; +import { ArtifactListPageService } from '../artifact-list-page/artifact-list-page.service'; @Component({ selector: 'artifact-additions', @@ -32,14 +33,21 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { @Input() tab: string; - @Input() currentTabLinkId: string = 'vulnerability'; + @Input() currentTabLinkId: string = ''; activeTab: string = null; @ViewChild('additionsTab') tabs: ClrTabs; - constructor(private ref: ChangeDetectorRef) {} + constructor( + private ref: ChangeDetectorRef, + private artifactListPageService: ArtifactListPageService + ) {} ngOnInit(): void { this.activeTab = this.tab; + if (!this.activeTab) { + this.currentTabLinkId = 'vulnerability'; + } + this.artifactListPageService.init(this.projectId); } ngAfterViewChecked() { @@ -50,6 +58,10 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { this.ref.detectChanges(); } + hasScannerSupportSBOM(): boolean { + return this.artifactListPageService.hasScannerSupportSBOM(); + } + getVulnerability(): AdditionLink { if ( this.additionLinks && @@ -59,12 +71,7 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { } return null; } - getSbom(): AdditionLink { - if (this.additionLinks && this.additionLinks[ADDITIONS.SBOMS]) { - return this.additionLinks[ADDITIONS.SBOMS]; - } - return {}; - } + getBuildHistory(): AdditionLink { if (this.additionLinks && this.additionLinks[ADDITIONS.BUILD_HISTORY]) { return this.additionLinks[ADDITIONS.BUILD_HISTORY]; @@ -93,4 +100,12 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { actionTab(tab: string): void { this.currentTabLinkId = tab; } + + getScanBtnState(): ClrLoadingState { + return this.artifactListPageService.getScanBtnState(); + } + + hasEnabledScanner(): boolean { + return this.artifactListPageService.hasEnabledScanner(); + } } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html index c7b9cf8a6..577711f33 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html @@ -32,12 +32,18 @@ - {{ - 'SBOM.GRID.COLUMN_PACKAGE' | translate - }} - {{ - 'SBOM.GRID.COLUMN_VERSION' | translate - }} + {{ 'SBOM.GRID.COLUMN_PACKAGE' | translate }} + {{ 'SBOM.GRID.COLUMN_VERSION' | translate }} {{ 'SBOM.GRID.COLUMN_LICENSE' | translate }} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts index e3978ad39..09e68430a 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts @@ -10,7 +10,6 @@ import { } from '@ngx-translate/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { UserPermissionService } from '../../../../../../shared/services'; -import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link'; import { ErrorHandler } from '../../../../../../shared/units/error-handler'; import { SessionService } from '../../../../../../shared/services/session.service'; import { SessionUser } from '../../../../../../shared/entities/session-user'; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts index ac352ff0f..c37ee3c16 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts @@ -1,13 +1,6 @@ -import { - AfterViewInit, - Component, - Input, - OnDestroy, - OnInit, -} from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular'; import { finalize } from 'rxjs/operators'; -import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link'; import { ScannerVo, UserPermissionService, @@ -30,7 +23,6 @@ import { HarborEvent, } from '../../../../../../services/event-service/event.service'; import { severityText } from '../../../../../left-side-nav/interrogation-services/vulnerability-database/security-hub.interface'; -import { AppConfigService } from 'src/app/services/app-config.service'; import { ArtifactSbom, @@ -38,8 +30,7 @@ import { getArtifactSbom, } from '../../artifact'; import { ArtifactService } from 'ng-swagger-gen/services'; -import { ScanTypes } from 'src/app/shared/entities/shared.const'; -import { ArtifactListPageService } from '../../artifact-list-page/artifact-list-page.service'; +import { ScanTypes } from '../../../../../../shared/entities/shared.const'; @Component({ selector: 'hbr-artifact-sbom', @@ -56,13 +47,12 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { @Input() sbomDigest: string; @Input() artifact: Artifact; + @Input() hasScannerSupportSBOM: boolean = false; artifactSbom: ArtifactSbom; loading: boolean = false; - hasScannerSupportSBOM: boolean = false; downloadSbomBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; hasSbomPermission: boolean = false; - hasShowLoading: boolean = false; sub: Subscription; hasViewInitWithDelay: boolean = false; @@ -73,16 +63,13 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { readonly severityText = severityText; constructor( private errorHandler: ErrorHandler, - private appConfigService: AppConfigService, private artifactService: ArtifactService, - private artifactListPageService: ArtifactListPageService, private userPermissionService: UserPermissionService, private eventService: EventService, private session: SessionService ) {} ngOnInit() { - this.artifactListPageService.init(this.projectId); this.getSbom(); this.getSbomPermission(); if (!this.sub) { @@ -222,8 +209,6 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { } canDownloadSbom(): boolean { - this.hasScannerSupportSBOM = - this.artifactListPageService.hasScannerSupportSBOM(); return ( this.hasScannerSupportSBOM && //this.hasSbomPermission && @@ -234,7 +219,12 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { } artifactSbomPackages(): ArtifactSbomPackageItem[] { - return this.artifactSbom?.sbomPackage?.packages ?? []; + return ( + this.artifactSbom?.sbomPackage?.packages?.filter( + item => + item?.name || item?.versionInfo || item?.licenseConcluded + ) ?? [] + ); } load(state: ClrDatagridStateInterface) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts index 02ad708ea..9d83d167c 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts @@ -50,14 +50,13 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { @Input() digest: string; @Input() artifact: Artifact; + @Input() scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; + @Input() hasEnabledScanner: boolean = false; scan_overview: any; scanner: ScannerVo; - projectScanner: ScannerVo; scanningResults: VulnerabilityItem[] = []; loading: boolean = false; - hasEnabledScanner: boolean = false; - scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; severitySort: ClrDatagridComparatorInterface; cvssSort: ClrDatagridComparatorInterface; hasScanningPermission: boolean = false; @@ -112,7 +111,6 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { ngOnInit() { this.getVulnerabilities(); this.getScanningPermission(); - this.getProjectScanner(); if (!this.sub) { this.sub = this.eventService.subscribe( HarborEvent.UPDATE_VULNERABILITY_INFO, @@ -203,30 +201,6 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { ); } - 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; - } - this.projectScanner = response; - }, - error => { - this.scanBtnState = ClrLoadingState.ERROR; - } - ); - } - getLevel(v: VulnerabilityItem): number { if (v && v.severity && SEVERITY_LEVEL_MAP[v.severity]) { return SEVERITY_LEVEL_MAP[v.severity]; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html index d523b67c1..f8d10c908 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html @@ -65,10 +65,6 @@ class="action-dropdown" clrPosition="bottom-left" *clrIfOpen> -