diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index bcdfde3d9..ab820223a 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -6940,6 +6940,7 @@ definitions: description: The operation's detail description operation_result: type: boolean + x-omitempty: false description: the operation's result, true for success, false for fail op_time: type: string diff --git a/src/common/rbac/project/rbac_role.go b/src/common/rbac/project/rbac_role.go index 961f00486..f71da22ba 100644 --- a/src/common/rbac/project/rbac_role.go +++ b/src/common/rbac/project/rbac_role.go @@ -127,8 +127,6 @@ var ( {Resource: rbac.ResourceMetadata, Action: rbac.ActionRead}, - {Resource: rbac.ResourceLog, Action: rbac.ActionList}, - {Resource: rbac.ResourceQuota, Action: rbac.ActionRead}, {Resource: rbac.ResourceLabel, Action: rbac.ActionCreate}, @@ -199,8 +197,6 @@ var ( {Resource: rbac.ResourceMember, Action: rbac.ActionRead}, {Resource: rbac.ResourceMember, Action: rbac.ActionList}, - {Resource: rbac.ResourceLog, Action: rbac.ActionList}, - {Resource: rbac.ResourceLabel, Action: rbac.ActionRead}, {Resource: rbac.ResourceLabel, Action: rbac.ActionList}, @@ -254,8 +250,6 @@ var ( {Resource: rbac.ResourceMember, Action: rbac.ActionRead}, {Resource: rbac.ResourceMember, Action: rbac.ActionList}, - {Resource: rbac.ResourceLog, Action: rbac.ActionList}, - {Resource: rbac.ResourceLabel, Action: rbac.ActionRead}, {Resource: rbac.ResourceLabel, Action: rbac.ActionList}, diff --git a/src/server/v2.0/handler/auditlog.go b/src/server/v2.0/handler/auditlog.go index a18105586..e0e2d6baa 100644 --- a/src/server/v2.0/handler/auditlog.go +++ b/src/server/v2.0/handler/auditlog.go @@ -28,21 +28,25 @@ import ( "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/audit" + "github.com/goharbor/harbor/src/pkg/auditext" + "github.com/goharbor/harbor/src/pkg/auditext/model" "github.com/goharbor/harbor/src/server/v2.0/models" "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/auditlog" ) func newAuditLogAPI() *auditlogAPI { return &auditlogAPI{ - auditMgr: audit.Mgr, - projectCtl: project.Ctl, + auditMgr: audit.Mgr, + auditextMgr: auditext.Mgr, + projectCtl: project.Ctl, } } type auditlogAPI struct { BaseAPI - auditMgr audit.Manager - projectCtl project.Controller + auditMgr audit.Manager + auditextMgr auditext.Manager + projectCtl project.Controller } func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAuditLogsParams) middleware.Responder { @@ -111,14 +115,85 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud WithPayload(auditLogs) } func (a *auditlogAPI) ListAuditLogExts(ctx context.Context, params auditlog.ListAuditLogExtsParams) middleware.Responder { - // TODO: implement this method + secCtx, ok := security.FromContext(ctx) + if !ok { + return a.SendError(ctx, errors.UnauthorizedError(errors.New("security context not found"))) + } + if !secCtx.IsAuthenticated() { + return a.SendError(ctx, errors.UnauthorizedError(nil).WithMessage(secCtx.GetUsername())) + } + query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize) + if err != nil { + return a.SendError(ctx, err) + } + + if err := a.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAuditLog); err != nil { + ol := &q.OrList{} + if sc, ok := secCtx.(*local.SecurityContext); ok && sc.IsAuthenticated() { + user := sc.User() + member := &project.MemberQuery{ + UserID: user.UserID, + GroupIDs: user.GroupIDs, + } + + projects, err := a.projectCtl.List(ctx, q.New(q.KeyWords{"member": member}), project.Metadata(false)) + if err != nil { + return a.SendError(ctx, fmt.Errorf( + "failed to get projects of user %s: %v", secCtx.GetUsername(), err)) + } + for _, project := range projects { + if a.HasProjectPermission(ctx, project.ProjectID, rbac.ActionList, rbac.ResourceLog) { + ol.Values = append(ol.Values, project.ProjectID) + } + } + } + // make sure no project will be selected with the query + if len(ol.Values) == 0 { + ol.Values = append(ol.Values, -1) + } + query.Keywords["ProjectID"] = ol + } + + total, err := a.auditextMgr.Count(ctx, query) + if err != nil { + return a.SendError(ctx, err) + } + logs, err := a.auditextMgr.List(ctx, query) + if err != nil { + return a.SendError(ctx, err) + } return auditlog.NewListAuditLogExtsOK(). - WithXTotalCount(0). - WithLink(a.Links(ctx, params.HTTPRequest.URL, 0, 0, 0).String()). - WithPayload(nil) + WithXTotalCount(total). + WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()). + WithPayload(convertToModelAuditLogExt(logs)) } -func (a *auditlogAPI) ListAuditLogEventTypes(_ context.Context, _ auditlog.ListAuditLogEventTypesParams) middleware.Responder { - // TODO: implement this method - return auditlog.NewListAuditLogEventTypesOK().WithPayload(nil) +func (a *auditlogAPI) ListAuditLogEventTypes(ctx context.Context, _ auditlog.ListAuditLogEventTypesParams) middleware.Responder { + if err := a.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAuditLog); err != nil { + return a.SendError(ctx, err) + } + var eventTypes []*models.AuditLogEventType + for _, v := range model.EventTypes { + eventTypes = append(eventTypes, &models.AuditLogEventType{ + EventType: v, + }) + } + return auditlog.NewListAuditLogEventTypesOK().WithPayload(eventTypes) +} + +func convertToModelAuditLogExt(logs []*model.AuditLogExt) []*models.AuditLogExt { + var auditLogs []*models.AuditLogExt + for _, log := range logs { + auditLogs = append(auditLogs, &models.AuditLogExt{ + ID: log.ID, + Resource: log.Resource, + ResourceType: log.ResourceType, + Username: log.Username, + Operation: log.Operation, + OperationDescription: log.OperationDescription, + OperationResult: log.OperationResult, + OpTime: strfmt.DateTime(log.OpTime), + }) + } + return auditLogs } diff --git a/src/server/v2.0/handler/project.go b/src/server/v2.0/handler/project.go index 22d432dfe..5faf77ce3 100644 --- a/src/server/v2.0/handler/project.go +++ b/src/server/v2.0/handler/project.go @@ -46,6 +46,7 @@ import ( "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg" "github.com/goharbor/harbor/src/pkg/audit" + "github.com/goharbor/harbor/src/pkg/auditext" "github.com/goharbor/harbor/src/pkg/member" "github.com/goharbor/harbor/src/pkg/project/metadata" pkgModels "github.com/goharbor/harbor/src/pkg/project/models" @@ -66,6 +67,7 @@ func newProjectAPI() *projectAPI { return &projectAPI{ auditMgr: audit.Mgr, artCtl: artifact.Ctl, + auditextMgr: auditext.Mgr, metadataMgr: pkg.ProjectMetaMgr, userCtl: user.Ctl, repositoryCtl: repository.Ctl, @@ -82,6 +84,7 @@ func newProjectAPI() *projectAPI { type projectAPI struct { BaseAPI auditMgr audit.Manager + auditextMgr auditext.Manager artCtl artifact.Controller metadataMgr metadata.Manager userCtl user.Controller @@ -940,9 +943,29 @@ func highestRole(roles []int) int { } func (a *projectAPI) GetLogExts(ctx context.Context, params operation.GetLogExtsParams) middleware.Responder { - // TODO: implement the function + if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionList, rbac.ResourceLog); err != nil { + return a.SendError(ctx, err) + } + pro, err := a.projectCtl.GetByName(ctx, params.ProjectName) + if err != nil { + return a.SendError(ctx, err) + } + query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize) + if err != nil { + return a.SendError(ctx, err) + } + query.Keywords["ProjectID"] = pro.ProjectID + + total, err := a.auditextMgr.Count(ctx, query) + if err != nil { + return a.SendError(ctx, err) + } + logs, err := a.auditextMgr.List(ctx, query) + if err != nil { + return a.SendError(ctx, err) + } return operation.NewGetLogExtsOK(). - WithXTotalCount(0). - WithLink(a.Links(ctx, params.HTTPRequest.URL, 0, 0, 15).String()). - WithPayload(nil) + WithXTotalCount(total). + WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()). + WithPayload(convertToModelAuditLogExt(logs)) } diff --git a/tests/apitests/python/test_assign_role_to_ldap_group.py b/tests/apitests/python/test_assign_role_to_ldap_group.py index 36015a4bd..4cd162ede 100644 --- a/tests/apitests/python/test_assign_role_to_ldap_group.py +++ b/tests/apitests/python/test_assign_role_to_ldap_group.py @@ -58,7 +58,7 @@ class TestAssignRoleToLdapGroup(unittest.TestCase): self.project.add_project_members(project_id, member_role_id = 3, _ldap_group_dn = "cn=harbor_guest,ou=groups,dc=example,dc=com", **ADMIN_CLIENT) projects = self.project.get_projects(dict(name=project_name), **USER_ADMIN) - self.assertTrue(len(projects) == 1) + self.assertTrue(len(projects) == 1) self.assertEqual(1, projects[0].current_user_role_id) #Mike has logged in harbor in previous test. @@ -80,8 +80,8 @@ class TestAssignRoleToLdapGroup(unittest.TestCase): self.assertTrue(len(artifacts) == 0) self.assertTrue(self.project.query_user_logs(project_name, **USER_ADMIN)>0, "admin user can see logs") - self.assertTrue(self.project.query_user_logs(project_name, **USER_DEV)>0, "dev user can see logs") - self.assertTrue(self.project.query_user_logs(project_name, **USER_GUEST)>0, "guest user can see logs") + self.assertTrue(self.project.query_user_logs(project_name, status_code=403, **USER_DEV)==0, "dev user can not see any logs") + self.assertTrue(self.project.query_user_logs(project_name, status_code=403, **USER_GUEST)==0, "guest user can not see any logs") self.assertTrue(self.project.query_user_logs(project_name, status_code=403, **USER_TEST)==0, "test user can not see any logs") self.repo.delete_repository(project_name, repo_name_admin.split('/')[1], **USER_ADMIN)