diff --git a/src/common/rbac/project/rbac_role.go b/src/common/rbac/project/rbac_role.go index b0adff006..542fc50ad 100644 --- a/src/common/rbac/project/rbac_role.go +++ b/src/common/rbac/project/rbac_role.go @@ -80,6 +80,7 @@ var ( {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate}, {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete}, + {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionList}, {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceConfiguration, Action: rbac.ActionUpdate}, @@ -180,6 +181,7 @@ var ( {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate}, {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete}, + {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionList}, {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, @@ -238,6 +240,7 @@ var ( {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate}, {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete}, + {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionList}, {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 4fbd13f97..b8746573a 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -633,11 +633,23 @@ func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact } // GetScanLog ... -func (bc *basicController) GetScanLog(ctx context.Context, uuid string) ([]byte, error) { +func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) { if len(uuid) == 0 { return nil, errors.New("empty uuid to get scan log") } + r, err := bc.sc.GetRegistrationByProject(ctx, artifact.ProjectID) + if err != nil { + return nil, err + } + artifacts, _, err := bc.collectScanningArtifacts(ctx, r, artifact) + if err != nil { + return nil, err + } + artifactMap := map[int64]interface{}{} + for _, a := range artifacts { + artifactMap[a.ID] = struct{}{} + } reportUUIDs := vuln.ParseReportIDs(uuid) tasks, err := bc.listScanTasks(ctx, reportUUIDs) if err != nil { @@ -649,9 +661,12 @@ func (bc *basicController) GetScanLog(ctx context.Context, uuid string) ([]byte, } reportUUIDToTasks := map[string]*task.Task{} - for _, task := range tasks { - for _, reportUUID := range getReportUUIDs(task.ExtraAttrs) { - reportUUIDToTasks[reportUUID] = task + for _, t := range tasks { + if !scanTaskForArtifacts(t, artifactMap) { + return nil, errors.NotFoundError(nil).WithMessage("scan log with uuid: %s not found", uuid) + } + for _, reportUUID := range getReportUUIDs(t.ExtraAttrs) { + reportUUIDToTasks[reportUUID] = t } } @@ -719,6 +734,18 @@ func (bc *basicController) GetScanLog(ctx context.Context, uuid string) ([]byte, return b.Bytes(), nil } +func scanTaskForArtifacts(task *task.Task, artifactMap map[int64]interface{}) bool { + if task == nil { + return false + } + artifactID := int64(task.GetNumFromExtraAttrs(artifactIDKey)) + if artifactID == 0 { + return false + } + _, exist := artifactMap[artifactID] + return exist +} + // DeleteReports ... func (bc *basicController) DeleteReports(ctx context.Context, digests ...string) error { if err := bc.manager.DeleteByDigests(ctx, digests...); err != nil { diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 020bd896e..ca0038736 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -31,6 +31,7 @@ import ( "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + art "github.com/goharbor/harbor/src/pkg/artifact" _ "github.com/goharbor/harbor/src/pkg/config/db" _ "github.com/goharbor/harbor/src/pkg/config/inmemory" "github.com/goharbor/harbor/src/pkg/permission/types" @@ -88,7 +89,7 @@ func (suite *ControllerTestSuite) SetupSuite() { suite.artifactCtl = &artifacttesting.Controller{} artifact.Ctl = suite.artifactCtl - suite.artifact = &artifact.Artifact{} + suite.artifact = &artifact.Artifact{Artifact: art.Artifact{ID: 1}} suite.artifact.Type = "IMAGE" suite.artifact.ProjectID = 1 suite.artifact.RepositoryName = "library/photon" @@ -320,7 +321,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() { }).Once() mock.OnAnything(suite.taskMgr, "List").Return([]*task.Task{ - {ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), Status: "Success"}, + {ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), Status: "Success"}, }, nil).Once() mock.OnAnything(suite.reportMgr, "Delete").Return(nil).Once() @@ -342,7 +343,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() { }).Once() mock.OnAnything(suite.taskMgr, "List").Return([]*task.Task{ - {ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), Status: "Success"}, + {ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), Status: "Success"}, }, nil).Once() mock.OnAnything(suite.reportMgr, "Delete").Return(fmt.Errorf("delete failed")).Once() @@ -359,7 +360,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() { }).Once() mock.OnAnything(suite.taskMgr, "List").Return([]*task.Task{ - {ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), Status: "Running"}, + {ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), Status: "Running"}, }, nil).Once() suite.Require().Error(suite.c.Scan(context.TODO(), suite.artifact)) @@ -376,7 +377,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { { // success mock.OnAnything(suite.execMgr, "List").Return([]*task.Execution{ - {ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), Status: "Running"}, + {ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), Status: "Running"}, }, nil).Once() mock.OnAnything(suite.execMgr, "Stop").Return(nil).Once() @@ -413,9 +414,9 @@ func (suite *ControllerTestSuite) TestScanControllerGetReport() { }).Once() mock.OnAnything(suite.taskMgr, "List").Return([]*task.Task{ - {ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001")}, + {ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001")}, }, nil).Once() - + mock.OnAnything(suite.accessoryMgr, "List").Return(nil, nil) rep, err := suite.c.GetReport(context.TODO(), suite.artifact, []string{v1.MimeTypeNativeReport}) require.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(rep)) @@ -440,13 +441,13 @@ func (suite *ControllerTestSuite) TestScanControllerGetScanLog() { mock.OnAnything(suite.taskMgr, "List").Return([]*task.Task{ { ID: 1, - ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), + ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), }, }, nil).Once() mock.OnAnything(suite.taskMgr, "GetLog").Return([]byte("log"), nil).Once() - bytes, err := suite.c.GetScanLog(context.TODO(), "rp-uuid-001") + bytes, err := suite.c.GetScanLog(context.TODO(), &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1}}, "rp-uuid-001") require.NoError(suite.T(), err) assert.Condition(suite.T(), func() (success bool) { success = len(bytes) > 0 @@ -459,23 +460,26 @@ func (suite *ControllerTestSuite) TestScanControllerGetMultiScanLog() { suite.taskMgr.On("List", context.TODO(), q.New(kw1)).Return([]*task.Task{ { ID: 1, - ExtraAttrs: suite.makeExtraAttrs("rp-uuid-001"), + ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-001"), }, }, nil).Times(4) - + mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) { + walkFn := args.Get(2).(func(*artifact.Artifact) error) + walkFn(suite.artifact) + }) + mock.OnAnything(suite.accessoryMgr, "List").Return(nil, nil) kw2 := q.KeyWords{"extra_attrs.report:rp-uuid-002": "1"} suite.taskMgr.On("List", context.TODO(), q.New(kw2)).Return([]*task.Task{ { ID: 2, - ExtraAttrs: suite.makeExtraAttrs("rp-uuid-002"), + ExtraAttrs: suite.makeExtraAttrs(int64(1), "rp-uuid-002"), }, }, nil).Times(4) - { // Both success mock.OnAnything(suite.taskMgr, "GetLog").Return([]byte("log"), nil).Twice() - bytes, err := suite.c.GetScanLog(context.TODO(), base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) + bytes, err := suite.c.GetScanLog(context.TODO(), &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1}}, base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) suite.Nil(err) suite.NotEmpty(bytes) suite.Contains(string(bytes), "Logs of report rp-uuid-001") @@ -487,7 +491,7 @@ func (suite *ControllerTestSuite) TestScanControllerGetMultiScanLog() { suite.taskMgr.On("GetLog", context.TODO(), int64(1)).Return([]byte("log"), nil).Once() suite.taskMgr.On("GetLog", context.TODO(), int64(2)).Return(nil, fmt.Errorf("failed")).Once() - bytes, err := suite.c.GetScanLog(context.TODO(), base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) + bytes, err := suite.c.GetScanLog(context.TODO(), &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1}}, base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) suite.Nil(err) suite.NotEmpty(bytes) suite.NotContains(string(bytes), "Logs of report rp-uuid-001") @@ -497,7 +501,7 @@ func (suite *ControllerTestSuite) TestScanControllerGetMultiScanLog() { // Both failed mock.OnAnything(suite.taskMgr, "GetLog").Return(nil, fmt.Errorf("failed")).Twice() - bytes, err := suite.c.GetScanLog(context.TODO(), base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) + bytes, err := suite.c.GetScanLog(context.TODO(), &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1}}, base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) suite.Error(err) suite.Empty(bytes) } @@ -506,7 +510,7 @@ func (suite *ControllerTestSuite) TestScanControllerGetMultiScanLog() { // Both empty mock.OnAnything(suite.taskMgr, "GetLog").Return(nil, nil).Twice() - bytes, err := suite.c.GetScanLog(context.TODO(), base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) + bytes, err := suite.c.GetScanLog(context.TODO(), &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1}}, base64.StdEncoding.EncodeToString([]byte("rp-uuid-001|rp-uuid-002"))) suite.Nil(err) suite.Empty(bytes) } @@ -578,11 +582,12 @@ func (suite *ControllerTestSuite) TestDeleteReports() { suite.Error(suite.c.DeleteReports(context.TODO(), "digest")) } -func (suite *ControllerTestSuite) makeExtraAttrs(reportUUIDs ...string) map[string]interface{} { +func (suite *ControllerTestSuite) makeExtraAttrs(artifactID int64, reportUUIDs ...string) map[string]interface{} { b, _ := json.Marshal(map[string]interface{}{reportUUIDsKey: reportUUIDs}) extraAttrs := map[string]interface{}{} json.Unmarshal(b, &extraAttrs) + extraAttrs[artifactIDKey] = float64(artifactID) return extraAttrs } diff --git a/src/controller/scan/controller.go b/src/controller/scan/controller.go index 773e83688..47b47ccdb 100644 --- a/src/controller/scan/controller.go +++ b/src/controller/scan/controller.go @@ -93,7 +93,7 @@ type Controller interface { // Returns: // []byte : the log text stream // error : non nil error if any errors occurred - GetScanLog(ctx context.Context, uuid string) ([]byte, error) + GetScanLog(ctx context.Context, art *artifact.Artifact, uuid string) ([]byte, error) // Delete the reports related with the specified digests // diff --git a/src/core/api/chart_label.go b/src/core/api/chart_label.go index c29116bd4..0e1b1edf7 100644 --- a/src/core/api/chart_label.go +++ b/src/core/api/chart_label.go @@ -107,5 +107,8 @@ func (cla *ChartLabelAPI) RemoveLabel() { // GetLabels gets labels for the specified chart version. func (cla *ChartLabelAPI) GetLabels() { + if !cla.requireAccess(rbac.ActionList) { + return + } cla.getLabelsOfResource(common.ResourceTypeChart, cla.chartFullName) } diff --git a/src/server/v2.0/handler/artifact.go b/src/server/v2.0/handler/artifact.go index e8b2d9c62..2d9dcf8e4 100644 --- a/src/server/v2.0/handler/artifact.go +++ b/src/server/v2.0/handler/artifact.go @@ -18,11 +18,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/goharbor/harbor/src/lib/q" "net/http" "strings" "time" + "github.com/goharbor/harbor/src/common" + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/label" + "github.com/docker/distribution/reference" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" @@ -49,23 +52,25 @@ import ( func newArtifactAPI() *artifactAPI { return &artifactAPI{ - accMgr: accessory.Mgr, - artCtl: artifact.Ctl, - proCtl: project.Ctl, - repoCtl: repository.Ctl, - scanCtl: scan.DefaultController, - tagCtl: tag.Ctl, + accMgr: accessory.Mgr, + artCtl: artifact.Ctl, + proCtl: project.Ctl, + repoCtl: repository.Ctl, + scanCtl: scan.DefaultController, + tagCtl: tag.Ctl, + labelMgr: label.Mgr, } } type artifactAPI struct { BaseAPI - accMgr accessory.Manager - artCtl artifact.Controller - proCtl project.Controller - repoCtl repository.Controller - scanCtl scan.Controller - tagCtl tag.Controller + accMgr accessory.Manager + artCtl artifact.Controller + proCtl project.Controller + repoCtl repository.Controller + scanCtl scan.Controller + tagCtl tag.Controller + labelMgr label.Manager } func (a *artifactAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder { @@ -453,7 +458,14 @@ func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAddit } func (a *artifactAPI) AddLabel(ctx context.Context, params operation.AddLabelParams) middleware.Responder { - if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, rbac.ResourceArtifactLabel); err != nil { + projectID, err := getProjectID(ctx, params.ProjectName) + if err != nil { + return a.SendError(ctx, err) + } + if err := a.RequireProjectAccess(ctx, projectID, rbac.ActionCreate, rbac.ResourceArtifactLabel); err != nil { + return a.SendError(ctx, err) + } + if err := a.RequireLabelInProject(ctx, projectID, params.Label.ID); err != nil { return a.SendError(ctx, err) } art, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, nil) @@ -480,6 +492,17 @@ func (a *artifactAPI) RemoveLabel(ctx context.Context, params operation.RemoveLa return operation.NewRemoveLabelOK() } +func (a *artifactAPI) RequireLabelInProject(ctx context.Context, projectID, labelID int64) error { + l, err := a.labelMgr.Get(ctx, labelID) + if err != nil { + return err + } + if l.Scope == common.LabelScopeProject && l.ProjectID != projectID { + return errors.NotFoundError(nil).WithMessage("project id %d, label %d not found", projectID, labelID) + } + return nil +} + func option(withTag, withImmutableStatus, withLabel, withSignature, withAccessory *bool) *artifact.Option { option := &artifact.Option{ WithTag: true, // return the tag by default diff --git a/src/server/v2.0/handler/scan.go b/src/server/v2.0/handler/scan.go index 78d9e72d5..eb393d584 100644 --- a/src/server/v2.0/handler/scan.go +++ b/src/server/v2.0/handler/scan.go @@ -92,14 +92,13 @@ func (s *scanAPI) GetReportLog(ctx context.Context, params operation.GetReportLo if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceScan); err != nil { return s.SendError(ctx, err) } - repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName) - _, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil) + a, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil) if err != nil { return s.SendError(ctx, err) } - bytes, err := s.scanCtl.GetScanLog(ctx, params.ReportID) + bytes, err := s.scanCtl.GetScanLog(ctx, a, params.ReportID) if err != nil { return s.SendError(ctx, err) } diff --git a/src/testing/controller/scan/controller.go b/src/testing/controller/scan/controller.go index b60c608ac..564baa452 100644 --- a/src/testing/controller/scan/controller.go +++ b/src/testing/controller/scan/controller.go @@ -65,13 +65,13 @@ func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mim return r0, r1 } -// GetScanLog provides a mock function with given fields: ctx, uuid -func (_m *Controller) GetScanLog(ctx context.Context, uuid string) ([]byte, error) { - ret := _m.Called(ctx, uuid) +// GetScanLog provides a mock function with given fields: ctx, art, uuid +func (_m *Controller) GetScanLog(ctx context.Context, art *artifact.Artifact, uuid string) ([]byte, error) { + ret := _m.Called(ctx, art, uuid) var r0 []byte - if rf, ok := ret.Get(0).(func(context.Context, string) []byte); ok { - r0 = rf(ctx, uuid) + if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string) []byte); ok { + r0 = rf(ctx, art, uuid) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]byte) @@ -79,8 +79,8 @@ func (_m *Controller) GetScanLog(ctx context.Context, uuid string) ([]byte, erro } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, uuid) + if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, string) error); ok { + r1 = rf(ctx, art, uuid) } else { r1 = ret.Error(1) }