From ec8d692fe6038b2d0f35310ec10fdc665ad0e1c1 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Thu, 25 Apr 2024 17:00:35 +0800 Subject: [PATCH 01/13] Add scanner info and report_id to sbom_overview on listing artifact (#20358) Add scan_status and report_id when scan has a failed task Signed-off-by: stonezdj --- api/v2.0/swagger.yaml | 2 ++ src/controller/scan/base_controller.go | 21 ++++++++++++++++ src/controller/scan/base_controller_test.go | 27 ++++++++++++++++++--- src/pkg/scan/sbom/model/summary.go | 4 +++ src/pkg/scan/sbom/sbom.go | 15 ++++++------ src/server/v2.0/handler/assembler/report.go | 22 ++++++++--------- 6 files changed, 68 insertions(+), 23 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 6890602a6..c9e1e8a50 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -6816,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/scan/base_controller.go b/src/controller/scan/base_controller.go index 161481711..c70221a0f 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -751,6 +751,11 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, reportContent := reports[0].Report result := map[string]interface{}{} if len(reportContent) == 0 { + 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 } @@ -758,6 +763,22 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, 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/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 a3e0f8501..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,7 +107,7 @@ 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 @@ -121,7 +121,7 @@ func (v *scanHandler) annotations() map[string]string { } } -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 @@ -130,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 @@ -150,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/server/v2.0/handler/assembler/report.go b/src/server/v2.0/handler/assembler/report.go index e4f9657ea..ff11e2b8f 100644 --- a/src/server/v2.0/handler/assembler/report.go +++ b/src/server/v2.0/handler/assembler/report.go @@ -21,16 +21,12 @@ import ( "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/log" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model" "github.com/goharbor/harbor/src/server/v2.0/handler/model" ) const ( vulnerabilitiesAddition = "vulnerabilities" - startTime = "start_time" - endTime = "end_time" - scanStatus = "scan_status" - sbomDigest = "sbom_digest" - duration = "duration" ) // NewScanReportAssembler returns vul assembler @@ -92,17 +88,19 @@ func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error { overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport}) if err != nil { log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err) - } else if len(overview) > 0 { + } + if len(overview) > 0 { artifact.SBOMOverView = map[string]interface{}{ - startTime: overview[startTime], - endTime: overview[endTime], - scanStatus: overview[scanStatus], - sbomDigest: overview[sbomDigest], - duration: overview[duration], + sbomModel.StartTime: overview[sbomModel.StartTime], + sbomModel.EndTime: overview[sbomModel.EndTime], + sbomModel.ScanStatus: overview[sbomModel.ScanStatus], + sbomModel.SBOMDigest: overview[sbomModel.SBOMDigest], + sbomModel.Duration: overview[sbomModel.Duration], + sbomModel.ReportID: overview[sbomModel.ReportID], + sbomModel.Scanner: overview[sbomModel.Scanner], } } } } - return nil } From 0e8dce72be06b5082cc4b867683a1a36bd2ed37e Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 10:52:11 +0800 Subject: [PATCH 02/13] fix: fresh scanner list when updating scanner (#20366) Signed-off-by: Shengwen Yu --- tests/resources/Harbor-Pages/Vulnerability_Elements.robot | 1 + tests/resources/Util.robot | 1 + tests/robot-cases/Group1-Nightly/Trivy.robot | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/resources/Harbor-Pages/Vulnerability_Elements.robot b/tests/resources/Harbor-Pages/Vulnerability_Elements.robot index e64594a87..593c9d1a7 100644 --- a/tests/resources/Harbor-Pages/Vulnerability_Elements.robot +++ b/tests/resources/Harbor-Pages/Vulnerability_Elements.robot @@ -51,3 +51,4 @@ ${scanner_password_input} //*[@id='scanner-password'] ${scanner_token_input} //*[@id='scanner-token'] ${scanner_apikey_input} //*[@id='scanner-apiKey'] ${scanner_set_default_btn} //*[@id='set-default'] +${scanner_list_refresh_btn} //span[@class='refresh-btn']//clr-icon[@role='none'] diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index 54972975c..d394a6430 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -76,6 +76,7 @@ Resource Harbor-Pages/Job_Service_Dashboard_Elements.robot Resource Harbor-Pages/SecurityHub.robot Resource Harbor-Pages/SecurityHub_Elements.robot Resource Harbor-Pages/Verify.robot +Resource Harbor-Pages/Vulnerability_Elements.robot Resource Docker-Util.robot Resource CNAB_Util.robot Resource Helm-Util.robot diff --git a/tests/robot-cases/Group1-Nightly/Trivy.robot b/tests/robot-cases/Group1-Nightly/Trivy.robot index 3db9513ed..bcfff07d2 100644 --- a/tests/robot-cases/Group1-Nightly/Trivy.robot +++ b/tests/robot-cases/Group1-Nightly/Trivy.robot @@ -182,7 +182,7 @@ Test Case - External Scanner CRUD Filter Scanner By Name scanner${d} Filter Scanner By Endpoint ${SCANNER_ENDPOINT} Retry Wait Element Count //clr-dg-row 1 - Retry Wait Until Page Contains Element //clr-dg-row[.//span[text()='scanner${d}'] and .//clr-dg-cell[text()='${SCANNER_ENDPOINT}'] and .//span[text()='Healthy'] and .//clr-dg-cell[text()='None']] + Retry Double Keywords When Error Retry Element Click xpath=${scanner_list_refresh_btn} Retry Wait Until Page Contains Element //clr-dg-row[.//span[text()='scanner${d}'] and .//clr-dg-cell[text()='${SCANNER_ENDPOINT}'] and .//span[text()='Healthy'] and .//clr-dg-cell[text()='None']] # Delete this scanner Delete Scanner scanner${d} Close Browser From d0cb200ed5591dbdcfbb794631317bd2460e7839 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 11:44:00 +0800 Subject: [PATCH 03/13] fix: update nightly test case for verifying audit log of image digest (#20354) Signed-off-by: Shengwen Yu --- tests/robot-cases/Group1-Nightly/Common.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index d6915db20..fdc12f54b 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -885,13 +885,13 @@ Test Case - Audit Log And Purge # pull artifact Docker Pull ${ip}/project${d}/${image}:${tag1} Docker Logout ${ip} - Verify Log ${user} project${d}/${image}:${sha256} artifact pull + Verify Log ${user} project${d}/${image}@${sha256} artifact pull Go Into Repo project${d} ${image} # delete artifact @{tag_list} Create List ${tag1} Multi-delete Artifact @{tag_list} Switch To Logs - Verify Log ${user} project${d}/${image}:${sha256} artifact delete + Verify Log ${user} project${d}/${image}@${sha256} artifact delete Go Into Project project${d} # delete repository Delete Repo project${d} ${image} From 822784aac81fbce27fd320e0008eea87ebf11ae2 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 12:28:22 +0800 Subject: [PATCH 04/13] =?UTF-8?q?fix:=20update=20to=20"clr-dg-cell[10]"=20?= =?UTF-8?q?to=20fix=20the=20pull=20time=20tc=20due=20to=20addin=E2=80=A6?= =?UTF-8?q?=20(#20361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: update to "clr-dg-cell[10]" to fix the pull time tc due to adding an SBOM column Signed-off-by: Shengwen Yu --- tests/robot-cases/Group1-Nightly/Common.robot | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index fdc12f54b..7730491e1 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -1151,8 +1151,8 @@ Test Case - Retain Image Last Pull Time Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] Should Be Empty ${last_pull_time} Switch To Configuration System Setting Set Up Retain Image Last Pull Time disable @@ -1160,8 +1160,8 @@ Test Case - Retain Image Last Pull Time Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] Should Not Be Empty ${last_pull_time} Close Browser From c791b39a26bf4b5a9c7f88d39025b531ab93f20d Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 14:13:00 +0800 Subject: [PATCH 05/13] fix: add stop_scan_payload when call stop scan api (#20353) Signed-off-by: Shengwen Yu --- tests/apitests/python/test_project_permission.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/apitests/python/test_project_permission.py b/tests/apitests/python/test_project_permission.py index 1962c48e3..491eba2dc 100644 --- a/tests/apitests/python/test_project_permission.py +++ b/tests/apitests/python/test_project_permission.py @@ -89,8 +89,11 @@ copy_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts?fr delete_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts/{}".format(harbor_base_url, project_name, source_artifact_tag), "DELETE", 200) # 6. Resource scan actions: ['read', 'create', 'stop'] +stop_scan_payload = { + "scan_type": "vulnerability" +} create_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) -stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) +stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202, stop_scan_payload) read_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/83be44fd-1234-5678-b49f-4b6d6e8f5730/log".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "get", 404) # 7. Resource tag actions: ['list', 'create', 'delete'] From dee73a44f30b4c60115157e53e3019ff758758b9 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:56:23 +0800 Subject: [PATCH 06/13] Fix UI bugs (#20364) Signed-off-by: xuelichao --- .../system-robot-util.ts | 1 + .../artifact-additions.component.html | 11 ++++++- .../artifact-additions.component.spec.ts | 21 ++++++++++++ .../artifact-additions.component.ts | 33 ++++++++++++++----- .../artifact-sbom.component.html | 18 ++++++---- .../artifact-sbom.component.spec.ts | 1 - .../artifact-sbom/artifact-sbom.component.ts | 28 +++++----------- .../artifact-vulnerabilities.component.ts | 30 ++--------------- .../artifact-list-tab.component.html | 4 --- .../artifact-list-tab.component.ts | 2 +- .../project/repository/artifact/artifact.ts | 2 +- .../sbom-tip-histogram.component.ts | 4 +-- .../src/app/shared/units/shared.utils.ts | 11 ++++++- src/portal/src/i18n/lang/de-de-lang.json | 3 +- src/portal/src/i18n/lang/en-us-lang.json | 3 +- src/portal/src/i18n/lang/es-es-lang.json | 3 +- src/portal/src/i18n/lang/fr-fr-lang.json | 3 +- src/portal/src/i18n/lang/ko-kr-lang.json | 3 +- src/portal/src/i18n/lang/pt-br-lang.json | 3 +- src/portal/src/i18n/lang/tr-tr-lang.json | 3 +- src/portal/src/i18n/lang/zh-cn-lang.json | 3 +- src/portal/src/i18n/lang/zh-tw-lang.json | 3 +- 22 files changed, 110 insertions(+), 83 deletions(-) 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> -