mirror of
https://github.com/goharbor/harbor
synced 2025-04-15 13:47:39 +00:00
Add user login event to audit log (#21415)
Add common event handler Register login event Update previous audit log event redirect to auditlogext table Signed-off-by: stonezdj <stone.zhang@broadcom.com>
This commit is contained in:
parent
39b2898e18
commit
f808f33cca
|
@ -16,12 +16,14 @@ package auditlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/controller/event"
|
"github.com/goharbor/harbor/src/controller/event"
|
||||||
|
evtModel "github.com/goharbor/harbor/src/controller/event/model"
|
||||||
"github.com/goharbor/harbor/src/lib/config"
|
"github.com/goharbor/harbor/src/lib/config"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/audit"
|
"github.com/goharbor/harbor/src/pkg/auditext"
|
||||||
am "github.com/goharbor/harbor/src/pkg/audit/model"
|
am "github.com/goharbor/harbor/src/pkg/auditext/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler - audit log handler
|
// Handler - audit log handler
|
||||||
|
@ -30,7 +32,7 @@ type Handler struct {
|
||||||
|
|
||||||
// AuditResolver - interface to resolve to AuditLog
|
// AuditResolver - interface to resolve to AuditLog
|
||||||
type AuditResolver interface {
|
type AuditResolver interface {
|
||||||
ResolveToAuditLog() (*am.AuditLog, error)
|
ResolveToAuditLog() (*am.AuditLogExt, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name ...
|
// Name ...
|
||||||
|
@ -40,13 +42,12 @@ func (h *Handler) Name() string {
|
||||||
|
|
||||||
// Handle ...
|
// Handle ...
|
||||||
func (h *Handler) Handle(ctx context.Context, value interface{}) error {
|
func (h *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||||
var auditLog *am.AuditLog
|
|
||||||
var addAuditLog bool
|
var addAuditLog bool
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case *event.PushArtifactEvent, *event.DeleteArtifactEvent,
|
case *event.PushArtifactEvent, *event.DeleteArtifactEvent,
|
||||||
*event.DeleteRepositoryEvent, *event.CreateProjectEvent, *event.DeleteProjectEvent,
|
*event.DeleteRepositoryEvent, *event.CreateProjectEvent, *event.DeleteProjectEvent,
|
||||||
*event.DeleteTagEvent, *event.CreateTagEvent,
|
*event.DeleteTagEvent, *event.CreateTagEvent,
|
||||||
*event.CreateRobotEvent, *event.DeleteRobotEvent:
|
*event.CreateRobotEvent, *event.DeleteRobotEvent, *evtModel.CommonEvent:
|
||||||
addAuditLog = true
|
addAuditLog = true
|
||||||
case *event.PullArtifactEvent:
|
case *event.PullArtifactEvent:
|
||||||
addAuditLog = !config.PullAuditLogDisable(ctx)
|
addAuditLog = !config.PullAuditLogDisable(ctx)
|
||||||
|
@ -56,14 +57,13 @@ func (h *Handler) Handle(ctx context.Context, value interface{}) error {
|
||||||
|
|
||||||
if addAuditLog {
|
if addAuditLog {
|
||||||
resolver := value.(AuditResolver)
|
resolver := value.(AuditResolver)
|
||||||
al, err := resolver.ResolveToAuditLog()
|
auditLog, err := resolver.ResolveToAuditLog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to handler event %v", err)
|
log.Errorf("failed to handler event %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
auditLog = al
|
if auditLog != nil && config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", auditLog.Operation, auditLog.ResourceType)) {
|
||||||
if auditLog != nil {
|
_, err := auditext.Mgr.Create(ctx, auditLog)
|
||||||
_, err := audit.Mgr.Create(ctx, auditLog)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("add audit log err: %v", err)
|
log.Debugf("add audit log err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ func init() {
|
||||||
_ = notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{})
|
_ = notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{})
|
||||||
_ = notifier.Subscribe(event.TopicCreateRobot, &auditlog.Handler{})
|
_ = notifier.Subscribe(event.TopicCreateRobot, &auditlog.Handler{})
|
||||||
_ = notifier.Subscribe(event.TopicDeleteRobot, &auditlog.Handler{})
|
_ = notifier.Subscribe(event.TopicDeleteRobot, &auditlog.Handler{})
|
||||||
|
_ = notifier.Subscribe(event.TopicCommonEvent, &auditlog.Handler{})
|
||||||
|
|
||||||
// internal
|
// internal
|
||||||
_ = notifier.Subscribe(event.TopicPullArtifact, &internal.ArtifactEventHandler{})
|
_ = notifier.Subscribe(event.TopicPullArtifact, &internal.ArtifactEventHandler{})
|
||||||
|
|
|
@ -62,8 +62,10 @@ type Metadata struct {
|
||||||
IPAddress string
|
IPAddress string
|
||||||
// ResponseLocation response location
|
// ResponseLocation response location
|
||||||
ResponseLocation string
|
ResponseLocation string
|
||||||
// ResourceName
|
// ResourceName resource name
|
||||||
ResourceName string
|
ResourceName string
|
||||||
|
// Payload request payload
|
||||||
|
Payload string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve parse the audit information from CommonEventMetadata
|
// Resolve parse the audit information from CommonEventMetadata
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import "github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/pkg/auditext/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||||
|
)
|
||||||
|
|
||||||
// Replication describes replication infos
|
// Replication describes replication infos
|
||||||
type Replication struct {
|
type Replication struct {
|
||||||
|
@ -80,3 +85,32 @@ type Scan struct {
|
||||||
// ScanType the scan type
|
// ScanType the scan type
|
||||||
ScanType string `json:"scan_type,omitempty"`
|
ScanType string `json:"scan_type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommonEvent ...
|
||||||
|
type CommonEvent struct {
|
||||||
|
Operator string
|
||||||
|
ProjectID int64
|
||||||
|
OcurrAt time.Time
|
||||||
|
Operation string
|
||||||
|
Payload string
|
||||||
|
SourceIP string
|
||||||
|
ResourceType string
|
||||||
|
ResourceName string
|
||||||
|
OperationDescription string
|
||||||
|
IsSuccessful bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveToAuditLog ...
|
||||||
|
func (c *CommonEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
|
auditLog := &model.AuditLogExt{
|
||||||
|
ProjectID: c.ProjectID,
|
||||||
|
OpTime: c.OcurrAt,
|
||||||
|
Operation: c.Operation,
|
||||||
|
Username: c.Operator,
|
||||||
|
ResourceType: c.ResourceType,
|
||||||
|
Resource: c.ResourceName,
|
||||||
|
OperationDescription: c.OperationDescription,
|
||||||
|
IsSuccessful: c.IsSuccessful,
|
||||||
|
}
|
||||||
|
return auditLog, nil
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/lib/selector"
|
"github.com/goharbor/harbor/src/lib/selector"
|
||||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||||
"github.com/goharbor/harbor/src/pkg/audit/model"
|
"github.com/goharbor/harbor/src/pkg/auditext/model"
|
||||||
proModels "github.com/goharbor/harbor/src/pkg/project/models"
|
proModels "github.com/goharbor/harbor/src/pkg/project/models"
|
||||||
robotModel "github.com/goharbor/harbor/src/pkg/robot/model"
|
robotModel "github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||||
|
@ -50,6 +50,12 @@ const (
|
||||||
TopicTagRetention = "TAG_RETENTION"
|
TopicTagRetention = "TAG_RETENTION"
|
||||||
TopicCreateRobot = "CREATE_ROBOT"
|
TopicCreateRobot = "CREATE_ROBOT"
|
||||||
TopicDeleteRobot = "DELETE_ROBOT"
|
TopicDeleteRobot = "DELETE_ROBOT"
|
||||||
|
TopicCommonEvent = "COMMON_API"
|
||||||
|
ResourceTypeProject = "project"
|
||||||
|
ResourceTypeArtifact = "artifact"
|
||||||
|
ResourceTypeRepository = "repository"
|
||||||
|
ResourceTypeRobot = "robot"
|
||||||
|
ResourceTypeTag = "tag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateProjectEvent is the creating project event
|
// CreateProjectEvent is the creating project event
|
||||||
|
@ -62,13 +68,15 @@ type CreateProjectEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: c.ProjectID,
|
ProjectID: c.ProjectID,
|
||||||
OpTime: c.OccurAt,
|
OpTime: c.OccurAt,
|
||||||
Operation: rbac.ActionCreate.String(),
|
Operation: rbac.ActionCreate.String(),
|
||||||
Username: c.Operator,
|
Username: c.Operator,
|
||||||
ResourceType: "project",
|
ResourceType: ResourceTypeProject,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("create project: %s", c.Project),
|
||||||
Resource: c.Project}
|
Resource: c.Project}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
@ -88,13 +96,15 @@ type DeleteProjectEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: d.ProjectID,
|
ProjectID: d.ProjectID,
|
||||||
OpTime: d.OccurAt,
|
OpTime: d.OccurAt,
|
||||||
Operation: rbac.ActionDelete.String(),
|
Operation: rbac.ActionDelete.String(),
|
||||||
Username: d.Operator,
|
Username: d.Operator,
|
||||||
ResourceType: "project",
|
ResourceType: ResourceTypeProject,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("delete project: %s", d.Project),
|
||||||
Resource: d.Project}
|
Resource: d.Project}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
@ -114,13 +124,15 @@ type DeleteRepositoryEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: d.ProjectID,
|
ProjectID: d.ProjectID,
|
||||||
OpTime: d.OccurAt,
|
OpTime: d.OccurAt,
|
||||||
Operation: rbac.ActionDelete.String(),
|
Operation: rbac.ActionDelete.String(),
|
||||||
Username: d.Operator,
|
Username: d.Operator,
|
||||||
ResourceType: "repository",
|
ResourceType: ResourceTypeRepository,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("delete repository: %s", d.Repository),
|
||||||
Resource: d.Repository,
|
Resource: d.Repository,
|
||||||
}
|
}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
|
@ -154,13 +166,15 @@ type PushArtifactEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: p.Artifact.ProjectID,
|
ProjectID: p.Artifact.ProjectID,
|
||||||
OpTime: p.OccurAt,
|
OpTime: p.OccurAt,
|
||||||
Operation: rbac.ActionCreate.String(),
|
Operation: rbac.ActionCreate.String(),
|
||||||
Username: p.Operator,
|
Username: p.Operator,
|
||||||
ResourceType: "artifact"}
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("push artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest),
|
||||||
|
ResourceType: ResourceTypeArtifact}
|
||||||
|
|
||||||
if len(p.Tags) == 0 {
|
if len(p.Tags) == 0 {
|
||||||
auditLog.Resource = fmt.Sprintf("%s@%s",
|
auditLog.Resource = fmt.Sprintf("%s@%s",
|
||||||
|
@ -183,13 +197,15 @@ type PullArtifactEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: p.Artifact.ProjectID,
|
ProjectID: p.Artifact.ProjectID,
|
||||||
OpTime: p.OccurAt,
|
OpTime: p.OccurAt,
|
||||||
Operation: rbac.ActionPull.String(),
|
Operation: rbac.ActionPull.String(),
|
||||||
Username: p.Operator,
|
Username: p.Operator,
|
||||||
ResourceType: "artifact"}
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("pull artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest),
|
||||||
|
ResourceType: ResourceTypeArtifact}
|
||||||
|
|
||||||
if len(p.Tags) == 0 {
|
if len(p.Tags) == 0 {
|
||||||
auditLog.Resource = fmt.Sprintf("%s@%s",
|
auditLog.Resource = fmt.Sprintf("%s@%s",
|
||||||
|
@ -219,13 +235,15 @@ type DeleteArtifactEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: d.Artifact.ProjectID,
|
ProjectID: d.Artifact.ProjectID,
|
||||||
OpTime: d.OccurAt,
|
OpTime: d.OccurAt,
|
||||||
Operation: rbac.ActionDelete.String(),
|
Operation: rbac.ActionDelete.String(),
|
||||||
Username: d.Operator,
|
Username: d.Operator,
|
||||||
ResourceType: "artifact",
|
ResourceType: ResourceTypeArtifact,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("delete artifact: %s@%s", d.Artifact.RepositoryName, d.Artifact.Digest),
|
||||||
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
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
@ -246,13 +264,15 @@ type CreateTagEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: c.AttachedArtifact.ProjectID,
|
ProjectID: c.AttachedArtifact.ProjectID,
|
||||||
OpTime: c.OccurAt,
|
OpTime: c.OccurAt,
|
||||||
Operation: rbac.ActionCreate.String(),
|
Operation: rbac.ActionCreate.String(),
|
||||||
Username: c.Operator,
|
Username: c.Operator,
|
||||||
ResourceType: "tag",
|
ResourceType: ResourceTypeTag,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("create tag: %s:%s", c.Repository, c.Tag),
|
||||||
Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)}
|
Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
@ -275,13 +295,14 @@ type DeleteTagEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: d.AttachedArtifact.ProjectID,
|
ProjectID: d.AttachedArtifact.ProjectID,
|
||||||
OpTime: d.OccurAt,
|
OpTime: d.OccurAt,
|
||||||
Operation: rbac.ActionDelete.String(),
|
Operation: rbac.ActionDelete.String(),
|
||||||
Username: d.Operator,
|
Username: d.Operator,
|
||||||
ResourceType: "tag",
|
ResourceType: ResourceTypeTag,
|
||||||
|
IsSuccessful: true,
|
||||||
Resource: fmt.Sprintf("%s:%s", d.Repository, d.Tag)}
|
Resource: fmt.Sprintf("%s:%s", d.Repository, d.Tag)}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
@ -385,13 +406,15 @@ type CreateRobotEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: c.Robot.ProjectID,
|
ProjectID: c.Robot.ProjectID,
|
||||||
OpTime: c.OccurAt,
|
OpTime: c.OccurAt,
|
||||||
Operation: rbac.ActionCreate.String(),
|
Operation: rbac.ActionCreate.String(),
|
||||||
Username: c.Operator,
|
Username: c.Operator,
|
||||||
ResourceType: "robot",
|
ResourceType: ResourceTypeRobot,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("create robot: %s", c.Robot.Name),
|
||||||
Resource: c.Robot.Name}
|
Resource: c.Robot.Name}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
@ -410,13 +433,15 @@ type DeleteRobotEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveToAuditLog ...
|
// ResolveToAuditLog ...
|
||||||
func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||||
auditLog := &model.AuditLog{
|
auditLog := &model.AuditLogExt{
|
||||||
ProjectID: c.Robot.ProjectID,
|
ProjectID: c.Robot.ProjectID,
|
||||||
OpTime: c.OccurAt,
|
OpTime: c.OccurAt,
|
||||||
Operation: rbac.ActionDelete.String(),
|
Operation: rbac.ActionDelete.String(),
|
||||||
Username: c.Operator,
|
Username: c.Operator,
|
||||||
ResourceType: "robot",
|
ResourceType: ResourceTypeRobot,
|
||||||
|
IsSuccessful: true,
|
||||||
|
OperationDescription: fmt.Sprintf("delete robot: %s", c.Robot.Name),
|
||||||
Resource: c.Robot.Name}
|
Resource: c.Robot.Name}
|
||||||
return auditLog, nil
|
return auditLog, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ import (
|
||||||
_ "github.com/goharbor/harbor/src/pkg/accessory/model/sbom"
|
_ "github.com/goharbor/harbor/src/pkg/accessory/model/sbom"
|
||||||
_ "github.com/goharbor/harbor/src/pkg/accessory/model/subject"
|
_ "github.com/goharbor/harbor/src/pkg/accessory/model/subject"
|
||||||
"github.com/goharbor/harbor/src/pkg/audit"
|
"github.com/goharbor/harbor/src/pkg/audit"
|
||||||
|
_ "github.com/goharbor/harbor/src/pkg/auditext/event/login"
|
||||||
dbCfg "github.com/goharbor/harbor/src/pkg/config/db"
|
dbCfg "github.com/goharbor/harbor/src/pkg/config/db"
|
||||||
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
|
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
|
||||||
"github.com/goharbor/harbor/src/pkg/notification"
|
"github.com/goharbor/harbor/src/pkg/notification"
|
||||||
|
|
|
@ -264,6 +264,9 @@ func BannerMessage(ctx context.Context) string {
|
||||||
|
|
||||||
// AuditLogEventEnabled returns the audit log enabled setting for a specific event_type, such as delete_user, create_user
|
// AuditLogEventEnabled returns the audit log enabled setting for a specific event_type, such as delete_user, create_user
|
||||||
func AuditLogEventEnabled(ctx context.Context, eventType string) bool {
|
func AuditLogEventEnabled(ctx context.Context, eventType string) bool {
|
||||||
|
if DefaultMgr() == nil || DefaultMgr().Get(ctx, common.AuditLogEventsDisabled) == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
disableListStr := DefaultMgr().Get(ctx, common.AuditLogEventsDisabled).GetString()
|
disableListStr := DefaultMgr().Get(ctx, common.AuditLogEventsDisabled).GetString()
|
||||||
disableList := strings.Split(disableListStr, ",")
|
disableList := strings.Split(disableListStr, ",")
|
||||||
for _, t := range disableList {
|
for _, t := range disableList {
|
||||||
|
|
|
@ -46,7 +46,7 @@ func (d *daoTestSuite) SetupSuite() {
|
||||||
Resource: "user01",
|
Resource: "user01",
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
OperationDescription: "Create user",
|
OperationDescription: "Create user",
|
||||||
OperationResult: true,
|
IsSuccessful: true,
|
||||||
OpTime: time.Now().AddDate(0, 0, -8),
|
OpTime: time.Now().AddDate(0, 0, -8),
|
||||||
})
|
})
|
||||||
d.Require().Nil(err)
|
d.Require().Nil(err)
|
||||||
|
@ -151,7 +151,7 @@ func (d *daoTestSuite) TestCreate() {
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
ResourceType: "user",
|
ResourceType: "user",
|
||||||
Resource: "user02",
|
Resource: "user02",
|
||||||
OperationResult: true,
|
IsSuccessful: true,
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
}
|
}
|
||||||
_, err := d.dao.Create(d.ctx, audit)
|
_, err := d.dao.Create(d.ctx, audit)
|
||||||
|
|
86
src/pkg/auditext/event/login/login.go
Normal file
86
src/pkg/auditext/event/login/login.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
ctlEvent "github.com/goharbor/harbor/src/controller/event"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/model"
|
||||||
|
"github.com/goharbor/harbor/src/lib/config"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var login = &loginResolver{}
|
||||||
|
var logout = &logoutResolver{}
|
||||||
|
commonevent.RegisterResolver(`/c/login$`, login)
|
||||||
|
commonevent.RegisterResolver(`/c/log_out$`, logout)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opLogout = "logout"
|
||||||
|
opLogin = "login"
|
||||||
|
logoutSuffix = "log_out"
|
||||||
|
payloadPattern = `principal=(.*?)&password`
|
||||||
|
)
|
||||||
|
|
||||||
|
type loginResolver struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loginResolver) Resolve(ce *commonevent.Metadata, event *event.Event) error {
|
||||||
|
e := &model.CommonEvent{
|
||||||
|
Operator: ce.Username,
|
||||||
|
ResourceType: rbac.ResourceUser.String(),
|
||||||
|
ResourceName: ce.Username,
|
||||||
|
OcurrAt: time.Now(),
|
||||||
|
Operation: opLogin,
|
||||||
|
OperationDescription: opLogin,
|
||||||
|
IsSuccessful: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the username from payload
|
||||||
|
re := regexp.MustCompile(payloadPattern)
|
||||||
|
if len(ce.RequestPayload) > 0 {
|
||||||
|
match := re.FindStringSubmatch(ce.RequestPayload)
|
||||||
|
if len(match) > 1 {
|
||||||
|
e.ResourceName = match[1]
|
||||||
|
e.Operator = match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ce.ResponseCode != http.StatusOK {
|
||||||
|
e.IsSuccessful = false
|
||||||
|
}
|
||||||
|
event.Topic = ctlEvent.TopicCommonEvent
|
||||||
|
event.Data = e
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loginResolver) PreCheck(ctx context.Context, _ string, method string) (bool, string) {
|
||||||
|
operation := ""
|
||||||
|
if method == http.MethodPost {
|
||||||
|
operation = opLogin
|
||||||
|
}
|
||||||
|
if len(operation) == 0 {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
return config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", operation, rbac.ResourceUser.String())), ""
|
||||||
|
}
|
109
src/pkg/auditext/event/login/login_test.go
Normal file
109
src/pkg/auditext/event/login/login_test.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_login_Resolve(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ce *commonevent.Metadata
|
||||||
|
event *event.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
l *loginResolver
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
wantUsername string
|
||||||
|
wantOperation string
|
||||||
|
wantOperationDescription string
|
||||||
|
wantIsSuccessful bool
|
||||||
|
}{
|
||||||
|
|
||||||
|
{"test normal", &loginResolver{}, args{
|
||||||
|
ce: &commonevent.Metadata{
|
||||||
|
Username: "test",
|
||||||
|
RequestURL: "/c/login",
|
||||||
|
RequestMethod: "POST",
|
||||||
|
Payload: "principal=test&password=123456",
|
||||||
|
ResponseCode: 200,
|
||||||
|
}, event: &event.Event{}}, false, "test", "login", "login", true},
|
||||||
|
{"test fail", &loginResolver{}, args{
|
||||||
|
ce: &commonevent.Metadata{
|
||||||
|
Username: "test",
|
||||||
|
RequestURL: "/c/login",
|
||||||
|
RequestMethod: "POST",
|
||||||
|
Payload: "principal=test&password=123456",
|
||||||
|
ResponseCode: 401,
|
||||||
|
}, event: &event.Event{}}, false, "test", "login", "login", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := &loginResolver{}
|
||||||
|
if err := l.Resolve(tt.args.ce, tt.args.event); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("resolver.Resolve() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).Operator != tt.wantUsername {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operator, tt.wantUsername)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).Operation != tt.wantOperation {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operation, tt.wantOperation)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).OperationDescription != tt.wantOperationDescription {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).OperationDescription, tt.wantOperationDescription)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).IsSuccessful != tt.wantIsSuccessful {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).IsSuccessful, tt.wantIsSuccessful)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_login_PreCheck(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
url string
|
||||||
|
method string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
e *loginResolver
|
||||||
|
args args
|
||||||
|
wantMatched bool
|
||||||
|
wantResourceName string
|
||||||
|
}{
|
||||||
|
{"test normal", &loginResolver{}, args{context.Background(), "/c/login", "POST"}, true, ""},
|
||||||
|
{"test fail method", &loginResolver{}, args{context.Background(), "/c/login", "PUT"}, false, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
e := &loginResolver{}
|
||||||
|
got, got1 := e.PreCheck(tt.args.ctx, tt.args.url, tt.args.method)
|
||||||
|
if got != tt.wantMatched {
|
||||||
|
t.Errorf("resolver.PreCheck() got = %v, want %v", got, tt.wantMatched)
|
||||||
|
}
|
||||||
|
if got1 != tt.wantResourceName {
|
||||||
|
t.Errorf("resolver.PreCheck() got1 = %v, want %v", got1, tt.wantResourceName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
61
src/pkg/auditext/event/login/logout.go
Normal file
61
src/pkg/auditext/event/login/logout.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
ctlEvent "github.com/goharbor/harbor/src/controller/event"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/model"
|
||||||
|
"github.com/goharbor/harbor/src/lib/config"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logoutResolver struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logoutResolver) Resolve(ce *commonevent.Metadata, event *event.Event) error {
|
||||||
|
e := &model.CommonEvent{
|
||||||
|
Operator: ce.Username,
|
||||||
|
ResourceType: rbac.ResourceUser.String(),
|
||||||
|
ResourceName: ce.Username,
|
||||||
|
OcurrAt: time.Now(),
|
||||||
|
Operation: opLogout,
|
||||||
|
OperationDescription: opLogout,
|
||||||
|
IsSuccessful: true,
|
||||||
|
}
|
||||||
|
if ce.ResponseCode != http.StatusOK {
|
||||||
|
e.IsSuccessful = false
|
||||||
|
}
|
||||||
|
event.Topic = ctlEvent.TopicCommonEvent
|
||||||
|
event.Data = e
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logoutResolver) PreCheck(ctx context.Context, _ string, method string) (bool, string) {
|
||||||
|
operation := ""
|
||||||
|
if method == http.MethodGet {
|
||||||
|
operation = opLogout
|
||||||
|
}
|
||||||
|
if len(operation) == 0 {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
return config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", operation, rbac.ResourceUser.String())), ""
|
||||||
|
}
|
100
src/pkg/auditext/event/login/logout_test.go
Normal file
100
src/pkg/auditext/event/login/logout_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||||
|
"github.com/goharbor/harbor/src/controller/event/model"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_logout_Resolve(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ce *commonevent.Metadata
|
||||||
|
event *event.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
l *logoutResolver
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
wantUsername string
|
||||||
|
wantOperation string
|
||||||
|
wantOperationDescription string
|
||||||
|
wantIsSuccessful bool
|
||||||
|
}{
|
||||||
|
{"test logout", &logoutResolver{}, args{
|
||||||
|
ce: &commonevent.Metadata{
|
||||||
|
Username: "test",
|
||||||
|
RequestURL: "/c/log_out",
|
||||||
|
RequestMethod: "GET",
|
||||||
|
Payload: "",
|
||||||
|
ResponseCode: 200,
|
||||||
|
}, event: &event.Event{}}, false, "test", "logout", "logout", true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := &logoutResolver{}
|
||||||
|
if err := l.Resolve(tt.args.ce, tt.args.event); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("resolver.Resolve() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).Operator != tt.wantUsername {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operator, tt.wantUsername)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).Operation != tt.wantOperation {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operation, tt.wantOperation)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).OperationDescription != tt.wantOperationDescription {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).OperationDescription, tt.wantOperationDescription)
|
||||||
|
}
|
||||||
|
if tt.args.event.Data.(*model.CommonEvent).IsSuccessful != tt.wantIsSuccessful {
|
||||||
|
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).IsSuccessful, tt.wantIsSuccessful)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_logout_PreCheck(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
url string
|
||||||
|
method string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
e *logoutResolver
|
||||||
|
args args
|
||||||
|
wantMatched bool
|
||||||
|
wantResourceName string
|
||||||
|
}{
|
||||||
|
{"test normal", &logoutResolver{}, args{context.Background(), "/c/log_out", "GET"}, true, ""},
|
||||||
|
{"test fail wrong url", &logoutResolver{}, args{context.Background(), "/c/logout", "DELETE"}, false, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
e := &logoutResolver{}
|
||||||
|
got, got1 := e.PreCheck(tt.args.ctx, tt.args.url, tt.args.method)
|
||||||
|
if got != tt.wantMatched {
|
||||||
|
t.Errorf("resolver.PreCheck() got = %v, want %v", got, tt.wantMatched)
|
||||||
|
}
|
||||||
|
if got1 != tt.wantResourceName {
|
||||||
|
t.Errorf("resolver.PreCheck() got1 = %v, want %v", got1, tt.wantResourceName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ type AuditLogExt struct {
|
||||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||||
Operation string `orm:"column(operation)" json:"operation"`
|
Operation string `orm:"column(operation)" json:"operation"`
|
||||||
OperationDescription string `orm:"column(op_desc)" json:"operation_description"`
|
OperationDescription string `orm:"column(op_desc)" json:"operation_description"`
|
||||||
OperationResult bool `orm:"column(op_result)" json:"operation_result"`
|
IsSuccessful bool `orm:"column(op_result)" json:"is_successful"`
|
||||||
ResourceType string `orm:"column(resource_type)" json:"resource_type"`
|
ResourceType string `orm:"column(resource_type)" json:"resource_type"`
|
||||||
Resource string `orm:"column(resource)" json:"resource"`
|
Resource string `orm:"column(resource)" json:"resource"`
|
||||||
Username string `orm:"column(username)" json:"username"`
|
Username string `orm:"column(username)" json:"username"`
|
||||||
|
|
|
@ -191,7 +191,7 @@ func convertToModelAuditLogExt(logs []*model.AuditLogExt) []*models.AuditLogExt
|
||||||
Username: log.Username,
|
Username: log.Username,
|
||||||
Operation: log.Operation,
|
Operation: log.Operation,
|
||||||
OperationDescription: log.OperationDescription,
|
OperationDescription: log.OperationDescription,
|
||||||
OperationResult: log.OperationResult,
|
OperationResult: log.IsSuccessful,
|
||||||
OpTime: strfmt.DateTime(log.OpTime),
|
OpTime: strfmt.DateTime(log.OpTime),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ class Project(base.Base):
|
||||||
base._assert_status_code(expect_status_code, status_code)
|
base._assert_status_code(expect_status_code, status_code)
|
||||||
|
|
||||||
def get_project_log(self, project_name, expect_status_code = 200, **kwargs):
|
def get_project_log(self, project_name, expect_status_code = 200, **kwargs):
|
||||||
body, status_code, _ = self._get_client(**kwargs).get_logs_with_http_info(project_name)
|
body, status_code, _ = self._get_client(**kwargs).get_log_exts_with_http_info(project_name)
|
||||||
base._assert_status_code(expect_status_code, status_code)
|
base._assert_status_code(expect_status_code, status_code)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class ProjectV2(base.Base, object):
|
||||||
super(ProjectV2,self).__init__(api_type = "projectv2")
|
super(ProjectV2,self).__init__(api_type = "projectv2")
|
||||||
|
|
||||||
def get_project_log(self, project_name, expect_status_code = 200, **kwargs):
|
def get_project_log(self, project_name, expect_status_code = 200, **kwargs):
|
||||||
body, status_code, _ = self._get_client(**kwargs).get_logs_with_http_info(project_name)
|
body, status_code, _ = self._get_client(**kwargs).get_log_exts_with_http_info(project_name)
|
||||||
base._assert_status_code(expect_status_code, status_code)
|
base._assert_status_code(expect_status_code, status_code)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user