Implement audit log ext API (#21414)

Signed-off-by: stonezdj <stone.zhang@broadcom.com>
This commit is contained in:
stonezdj(Daojun Zhang) 2025-01-16 17:23:25 +08:00 committed by GitHub
parent 3b655213c0
commit 4d5bc19866
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 117 additions and 24 deletions

View File

@ -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

View File

@ -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},

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)