From 1df7492d6b1ce8a9ca740eb210549c50b1c289af Mon Sep 17 00:00:00 2001 From: stonezdj Date: Tue, 14 Jan 2025 14:05:35 +0800 Subject: [PATCH] Add user login event to audit log Add common event handler Register login event Update previous audit log event redirect to auditlogext table Signed-off-by: stonezdj --- .../event/handler/auditlog/auditlog.go | 16 +- src/controller/event/handler/init.go | 1 + .../event/metadata/commonevent/model.go | 4 +- src/controller/event/model/event.go | 36 +++- src/controller/event/topic.go | 178 ++++++++++-------- src/core/main.go | 1 + src/lib/config/userconfig.go | 3 + src/pkg/auditext/event/login/login.go | 93 +++++++++ src/pkg/auditext/event/login/login_test.go | 105 +++++++++++ 9 files changed, 349 insertions(+), 88 deletions(-) create mode 100644 src/pkg/auditext/event/login/login.go create mode 100644 src/pkg/auditext/event/login/login_test.go diff --git a/src/controller/event/handler/auditlog/auditlog.go b/src/controller/event/handler/auditlog/auditlog.go index ef7cdbdda56..09a359b7e3c 100644 --- a/src/controller/event/handler/auditlog/auditlog.go +++ b/src/controller/event/handler/auditlog/auditlog.go @@ -16,12 +16,14 @@ package auditlog import ( "context" + "fmt" "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/log" - "github.com/goharbor/harbor/src/pkg/audit" - am "github.com/goharbor/harbor/src/pkg/audit/model" + "github.com/goharbor/harbor/src/pkg/auditext" + am "github.com/goharbor/harbor/src/pkg/auditext/model" ) // Handler - audit log handler @@ -30,7 +32,7 @@ type Handler struct { // AuditResolver - interface to resolve to AuditLog type AuditResolver interface { - ResolveToAuditLog() (*am.AuditLog, error) + ResolveToAuditLog() (*am.AuditLogExt, error) } // Name ... @@ -40,13 +42,13 @@ func (h *Handler) Name() string { // Handle ... func (h *Handler) Handle(ctx context.Context, value interface{}) error { - var auditLog *am.AuditLog + var auditLog *am.AuditLogExt var addAuditLog bool switch v := value.(type) { case *event.PushArtifactEvent, *event.DeleteArtifactEvent, *event.DeleteRepositoryEvent, *event.CreateProjectEvent, *event.DeleteProjectEvent, *event.DeleteTagEvent, *event.CreateTagEvent, - *event.CreateRobotEvent, *event.DeleteRobotEvent: + *event.CreateRobotEvent, *event.DeleteRobotEvent, *evtModel.CommonEvent: addAuditLog = true case *event.PullArtifactEvent: addAuditLog = !config.PullAuditLogDisable(ctx) @@ -62,8 +64,8 @@ func (h *Handler) Handle(ctx context.Context, value interface{}) error { return err } auditLog = al - if auditLog != nil { - _, err := audit.Mgr.Create(ctx, auditLog) + if auditLog != nil && config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", auditLog.Operation, auditLog.ResourceType)) { + _, err := auditext.Mgr.Create(ctx, auditLog) if err != nil { log.Debugf("add audit log err: %v", err) } diff --git a/src/controller/event/handler/init.go b/src/controller/event/handler/init.go index 841847a5ccf..ecfc527cc35 100644 --- a/src/controller/event/handler/init.go +++ b/src/controller/event/handler/init.go @@ -67,6 +67,7 @@ func init() { _ = notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{}) _ = notifier.Subscribe(event.TopicCreateRobot, &auditlog.Handler{}) _ = notifier.Subscribe(event.TopicDeleteRobot, &auditlog.Handler{}) + _ = notifier.Subscribe(event.TopicCommonEvent, &auditlog.Handler{}) // internal _ = notifier.Subscribe(event.TopicPullArtifact, &internal.ArtifactEventHandler{}) diff --git a/src/controller/event/metadata/commonevent/model.go b/src/controller/event/metadata/commonevent/model.go index 8a92544fdaf..ac12bb3c03d 100644 --- a/src/controller/event/metadata/commonevent/model.go +++ b/src/controller/event/metadata/commonevent/model.go @@ -62,8 +62,10 @@ type Metadata struct { IPAddress string // ResponseLocation response location ResponseLocation string - // ResourceName + // ResourceName resource name ResourceName string + // Payload request payload + Payload string } // Resolve parse the audit information from CommonEventMetadata diff --git a/src/controller/event/model/event.go b/src/controller/event/model/event.go index 2e7021bc36d..1a87cac2fb8 100644 --- a/src/controller/event/model/event.go +++ b/src/controller/event/model/event.go @@ -14,7 +14,12 @@ 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 type Replication struct { @@ -80,3 +85,32 @@ type Scan struct { // ScanType the scan type 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 + OperationResult 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, + OperationResult: c.OperationResult, + } + return auditLog, nil +} diff --git a/src/controller/event/topic.go b/src/controller/event/topic.go index f8f64b42060..2a2bcd884ee 100644 --- a/src/controller/event/topic.go +++ b/src/controller/event/topic.go @@ -21,7 +21,7 @@ import ( "github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/lib/selector" "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" robotModel "github.com/goharbor/harbor/src/pkg/robot/model" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" @@ -50,6 +50,7 @@ const ( TopicTagRetention = "TAG_RETENTION" TopicCreateRobot = "CREATE_ROBOT" TopicDeleteRobot = "DELETE_ROBOT" + TopicCommonEvent = "COMMON_API" ) // CreateProjectEvent is the creating project event @@ -62,14 +63,16 @@ type CreateProjectEvent struct { } // ResolveToAuditLog ... -func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: c.ProjectID, - OpTime: c.OccurAt, - Operation: rbac.ActionCreate.String(), - Username: c.Operator, - ResourceType: "project", - Resource: c.Project} +func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: c.ProjectID, + OpTime: c.OccurAt, + Operation: rbac.ActionCreate.String(), + Username: c.Operator, + ResourceType: "project", + OperationResult: true, + OperationDescription: fmt.Sprintf("create project: %s", c.Project), + Resource: c.Project} return auditLog, nil } @@ -88,14 +91,16 @@ type DeleteProjectEvent struct { } // ResolveToAuditLog ... -func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: d.ProjectID, - OpTime: d.OccurAt, - Operation: rbac.ActionDelete.String(), - Username: d.Operator, - ResourceType: "project", - Resource: d.Project} +func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: d.ProjectID, + OpTime: d.OccurAt, + Operation: rbac.ActionDelete.String(), + Username: d.Operator, + ResourceType: "project", + OperationResult: true, + OperationDescription: fmt.Sprintf("delete project: %s", d.Project), + Resource: d.Project} return auditLog, nil } @@ -114,14 +119,16 @@ type DeleteRepositoryEvent struct { } // ResolveToAuditLog ... -func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: d.ProjectID, - OpTime: d.OccurAt, - Operation: rbac.ActionDelete.String(), - Username: d.Operator, - ResourceType: "repository", - Resource: d.Repository, +func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: d.ProjectID, + OpTime: d.OccurAt, + Operation: rbac.ActionDelete.String(), + Username: d.Operator, + ResourceType: "repository", + OperationResult: true, + OperationDescription: fmt.Sprintf("delete repository: %s", d.Repository), + Resource: d.Repository, } return auditLog, nil } @@ -154,13 +161,15 @@ type PushArtifactEvent struct { } // ResolveToAuditLog ... -func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: p.Artifact.ProjectID, - OpTime: p.OccurAt, - Operation: rbac.ActionCreate.String(), - Username: p.Operator, - ResourceType: "artifact"} +func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: p.Artifact.ProjectID, + OpTime: p.OccurAt, + Operation: rbac.ActionCreate.String(), + Username: p.Operator, + OperationResult: true, + OperationDescription: fmt.Sprintf("push artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest), + ResourceType: "artifact"} if len(p.Tags) == 0 { auditLog.Resource = fmt.Sprintf("%s@%s", @@ -183,13 +192,15 @@ type PullArtifactEvent struct { } // ResolveToAuditLog ... -func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: p.Artifact.ProjectID, - OpTime: p.OccurAt, - Operation: rbac.ActionPull.String(), - Username: p.Operator, - ResourceType: "artifact"} +func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: p.Artifact.ProjectID, + OpTime: p.OccurAt, + Operation: rbac.ActionPull.String(), + Username: p.Operator, + OperationResult: true, + OperationDescription: fmt.Sprintf("pull artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest), + ResourceType: "artifact"} if len(p.Tags) == 0 { auditLog.Resource = fmt.Sprintf("%s@%s", @@ -219,14 +230,16 @@ type DeleteArtifactEvent struct { } // ResolveToAuditLog ... -func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: d.Artifact.ProjectID, - OpTime: d.OccurAt, - Operation: rbac.ActionDelete.String(), - Username: d.Operator, - ResourceType: "artifact", - Resource: fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)} +func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: d.Artifact.ProjectID, + OpTime: d.OccurAt, + Operation: rbac.ActionDelete.String(), + Username: d.Operator, + ResourceType: "artifact", + OperationResult: 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)} return auditLog, nil } @@ -246,14 +259,16 @@ type CreateTagEvent struct { } // ResolveToAuditLog ... -func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: c.AttachedArtifact.ProjectID, - OpTime: c.OccurAt, - Operation: rbac.ActionCreate.String(), - Username: c.Operator, - ResourceType: "tag", - Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)} +func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: c.AttachedArtifact.ProjectID, + OpTime: c.OccurAt, + Operation: rbac.ActionCreate.String(), + Username: c.Operator, + ResourceType: "tag", + OperationResult: true, + OperationDescription: fmt.Sprintf("create tag: %s:%s", c.Repository, c.Tag), + Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)} return auditLog, nil } @@ -275,14 +290,15 @@ type DeleteTagEvent struct { } // ResolveToAuditLog ... -func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: d.AttachedArtifact.ProjectID, - OpTime: d.OccurAt, - Operation: rbac.ActionDelete.String(), - Username: d.Operator, - ResourceType: "tag", - Resource: fmt.Sprintf("%s:%s", d.Repository, d.Tag)} +func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: d.AttachedArtifact.ProjectID, + OpTime: d.OccurAt, + Operation: rbac.ActionDelete.String(), + Username: d.Operator, + ResourceType: "tag", + OperationResult: true, + Resource: fmt.Sprintf("%s:%s", d.Repository, d.Tag)} return auditLog, nil } @@ -385,14 +401,16 @@ type CreateRobotEvent struct { } // ResolveToAuditLog ... -func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: c.Robot.ProjectID, - OpTime: c.OccurAt, - Operation: rbac.ActionCreate.String(), - Username: c.Operator, - ResourceType: "robot", - Resource: c.Robot.Name} +func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: c.Robot.ProjectID, + OpTime: c.OccurAt, + Operation: rbac.ActionCreate.String(), + Username: c.Operator, + ResourceType: "robot", + OperationResult: true, + OperationDescription: fmt.Sprintf("create robot: %s", c.Robot.Name), + Resource: c.Robot.Name} return auditLog, nil } @@ -410,14 +428,16 @@ type DeleteRobotEvent struct { } // ResolveToAuditLog ... -func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) { - auditLog := &model.AuditLog{ - ProjectID: c.Robot.ProjectID, - OpTime: c.OccurAt, - Operation: rbac.ActionDelete.String(), - Username: c.Operator, - ResourceType: "robot", - Resource: c.Robot.Name} +func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLogExt, error) { + auditLog := &model.AuditLogExt{ + ProjectID: c.Robot.ProjectID, + OpTime: c.OccurAt, + Operation: rbac.ActionDelete.String(), + Username: c.Operator, + ResourceType: "robot", + OperationResult: true, + OperationDescription: fmt.Sprintf("delete robot: %s", c.Robot.Name), + Resource: c.Robot.Name} return auditLog, nil } diff --git a/src/core/main.go b/src/core/main.go index f0bc9656452..cec2c4f05c0 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -63,6 +63,7 @@ import ( _ "github.com/goharbor/harbor/src/pkg/accessory/model/sbom" _ "github.com/goharbor/harbor/src/pkg/accessory/model/subject" "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" _ "github.com/goharbor/harbor/src/pkg/config/inmemory" "github.com/goharbor/harbor/src/pkg/notification" diff --git a/src/lib/config/userconfig.go b/src/lib/config/userconfig.go index 1937b3b7e95..b9fa3e45de6 100644 --- a/src/lib/config/userconfig.go +++ b/src/lib/config/userconfig.go @@ -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 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() disableList := strings.Split(disableListStr, ",") for _, t := range disableList { diff --git a/src/pkg/auditext/event/login/login.go b/src/pkg/auditext/event/login/login.go new file mode 100644 index 00000000000..ff730a913d6 --- /dev/null +++ b/src/pkg/auditext/event/login/login.go @@ -0,0 +1,93 @@ +// 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" + event2 "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 loginResolver = &resolver{} + commonevent.RegisterResolver(`/c/login$`, loginResolver) + commonevent.RegisterResolver(`/c/log_out$`, loginResolver) +} + +const ( + opLogout = "logout" + opLogin = "login" + logoutSuffix = "log_out" + payloadPattern = `principal=(.*?)&password` +) + +type resolver struct { +} + +func (l *resolver) Resolve(ce *commonevent.Metadata, event *event.Event) error { + e := &model.CommonEvent{ + Operator: ce.Username, + ResourceType: rbac.ResourceUser.String(), + ResourceName: ce.Username, + OcurrAt: time.Now(), + } + + // method POST for login, method GET for logout + if ce.RequestMethod == http.MethodGet { + e.Operation = opLogout + e.OperationDescription = opLogout + } else { + e.Operation = opLogin + e.OperationDescription = opLogin + // 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] + } + } + } + e.OperationResult = true + if ce.ResponseCode != http.StatusOK { + e.OperationResult = false + } + event.Topic = event2.TopicCommonEvent + event.Data = e + return nil +} +func (e *resolver) PreCheck(ctx context.Context, url string, method string) (bool, string) { + operation := "" + switch method { + case http.MethodPost: + operation = opLogin + case http.MethodGet: + operation = opLogout + } + if len(operation) == 0 { + return false, "" + } + return config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", operation, rbac.ResourceUser.String())), "" +} diff --git a/src/pkg/auditext/event/login/login_test.go b/src/pkg/auditext/event/login/login_test.go new file mode 100644 index 00000000000..86418f364dc --- /dev/null +++ b/src/pkg/auditext/event/login/login_test.go @@ -0,0 +1,105 @@ +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_resolver_Resolve(t *testing.T) { + type args struct { + ce *commonevent.Metadata + event *event.Event + } + tests := []struct { + name string + l *resolver + args args + wantErr bool + wantUsername string + wantOperation string + wantOperationDescription string + wantOperationResult bool + }{ + + {"test normal", &resolver{}, 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", &resolver{}, 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}, + {"test logout", &resolver{}, 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 := &resolver{} + 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).OperationResult != tt.wantOperationResult { + t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).OperationResult, tt.wantOperationResult) + } + }) + } +} + +func Test_resolver_PreCheck(t *testing.T) { + type args struct { + ctx context.Context + url string + method string + } + tests := []struct { + name string + e *resolver + args args + wantMatched bool + wantResourceName string + }{ + {"test normal", &resolver{}, args{context.Background(), "/c/login", "POST"}, true, ""}, + {"test fail", &resolver{}, args{context.Background(), "/c/logout", "GET"}, true, ""}, + {"test fail method", &resolver{}, args{context.Background(), "/c/login", "PUT"}, false, ""}, + {"test fail wrong url", &resolver{}, args{context.Background(), "/c/logout", "DELETE"}, false, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &resolver{} + 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) + } + }) + } +}