diff --git a/event_parsing.go b/event_parsing.go index 3943abadb..0c3f2ffed 100644 --- a/event_parsing.go +++ b/event_parsing.go @@ -27,24 +27,25 @@ type EventType string // List of available event types. const ( - EventConfidentialIssue EventType = "Confidential Issue Hook" - EventConfidentialNote EventType = "Confidential Note Hook" - EventTypeBuild EventType = "Build Hook" - EventTypeDeployment EventType = "Deployment Hook" - EventTypeFeatureFlag EventType = "Feature Flag Hook" - EventTypeIssue EventType = "Issue Hook" - EventTypeJob EventType = "Job Hook" - EventTypeMember EventType = "Member Hook" - EventTypeMergeRequest EventType = "Merge Request Hook" - EventTypeNote EventType = "Note Hook" - EventTypePipeline EventType = "Pipeline Hook" - EventTypePush EventType = "Push Hook" - EventTypeRelease EventType = "Release Hook" - EventTypeServiceHook EventType = "Service Hook" - EventTypeSubGroup EventType = "Subgroup Hook" - EventTypeSystemHook EventType = "System Hook" - EventTypeTagPush EventType = "Tag Push Hook" - EventTypeWikiPage EventType = "Wiki Page Hook" + EventConfidentialIssue EventType = "Confidential Issue Hook" + EventConfidentialNote EventType = "Confidential Note Hook" + EventTypeBuild EventType = "Build Hook" + EventTypeDeployment EventType = "Deployment Hook" + EventTypeFeatureFlag EventType = "Feature Flag Hook" + EventTypeIssue EventType = "Issue Hook" + EventTypeJob EventType = "Job Hook" + EventTypeMember EventType = "Member Hook" + EventTypeMergeRequest EventType = "Merge Request Hook" + EventTypeNote EventType = "Note Hook" + EventTypePipeline EventType = "Pipeline Hook" + EventTypePush EventType = "Push Hook" + EventTypeRelease EventType = "Release Hook" + EventTypeResourceAccessToken EventType = "Resource Access Token Hook" + EventTypeServiceHook EventType = "Service Hook" + EventTypeSubGroup EventType = "Subgroup Hook" + EventTypeSystemHook EventType = "System Hook" + EventTypeTagPush EventType = "Tag Push Hook" + EventTypeWikiPage EventType = "Wiki Page Hook" ) const ( @@ -252,6 +253,24 @@ func ParseWebhook(eventType EventType, payload []byte) (event interface{}, err e event = &PushEvent{} case EventTypeRelease: event = &ReleaseEvent{} + case EventTypeResourceAccessToken: + data := map[string]interface{}{} + err := json.Unmarshal(payload, &data) + if err != nil { + return nil, err + } + + _, groupEvent := data["group"] + _, projectEvent := data["project"] + + switch { + case groupEvent: + event = &GroupResourceAccessTokenEvent{} + case projectEvent: + event = &ProjectResourceAccessTokenEvent{} + default: + return nil, fmt.Errorf("unexpected resource access token payload") + } case EventTypeServiceHook: service := &serviceEvent{} err := json.Unmarshal(payload, service) diff --git a/event_parsing_webhook_test.go b/event_parsing_webhook_test.go index c9d6b9d8b..163eeff1d 100644 --- a/event_parsing_webhook_test.go +++ b/event_parsing_webhook_test.go @@ -101,7 +101,7 @@ func TestParseCommitCommentHook(t *testing.T) { } } -func TestParseFeatureFLagHook(t *testing.T) { +func TestParseFeatureFlagHook(t *testing.T) { raw := loadFixture("testdata/webhooks/feature_flag.json") parsedEvent, err := ParseWebhook("Feature Flag Hook", raw) @@ -147,6 +147,24 @@ func TestParseFeatureFLagHook(t *testing.T) { } } +func TestParseGroupResourceAccessTokenHook(t *testing.T) { + raw := loadFixture("testdata/webhooks/resource_access_token_group.json") + + parsedEvent, err := ParseWebhook("Resource Access Token Hook", raw) + if err != nil { + t.Errorf("Error parsing group resource access token hook: %s", err) + } + + event, ok := parsedEvent.(*GroupResourceAccessTokenEvent) + if !ok { + t.Errorf("Expected GroupResourceAccessTokenEvent, but parsing produced %T", parsedEvent) + } + + if event.GroupID != 35 { + t.Errorf("GroupID is %v, want %v", event.GroupID, 35) + } +} + func TestParseHookWebHook(t *testing.T) { parsedEvent1, err := ParseHook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json")) if err != nil { @@ -351,6 +369,24 @@ func TestParsePipelineHook(t *testing.T) { } } +func TestParseProjectResourceAccessTokenHook(t *testing.T) { + raw := loadFixture("testdata/webhooks/resource_access_token_project.json") + + parsedEvent, err := ParseWebhook("Resource Access Token Hook", raw) + if err != nil { + t.Errorf("Error parsing project resource access token hook: %s", err) + } + + event, ok := parsedEvent.(*ProjectResourceAccessTokenEvent) + if !ok { + t.Errorf("Expected ProjectResourceAccessTokenEvent, but parsing produced %T", parsedEvent) + } + + if event.ProjectID != 7 { + t.Errorf("ProjectID is %v, want %v", event.ProjectID, 7) + } +} + func TestParsePushHook(t *testing.T) { raw := loadFixture("testdata/webhooks/push.json") diff --git a/event_webhook_types.go b/event_webhook_types.go index 01b098308..b75a8eb31 100644 --- a/event_webhook_types.go +++ b/event_webhook_types.go @@ -131,7 +131,7 @@ type CommitCommentEvent struct { } `json:"commit"` } -// DeploymentEvent represents a deployment event +// DeploymentEvent represents a deployment event. // // GitLab API docs: // https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#deployment-events @@ -171,7 +171,7 @@ type DeploymentEvent struct { CommitTitle string `json:"commit_title"` } -// FeatureFlagEvent represents a feature flag event +// FeatureFlagEvent represents a feature flag event. // // GitLab API docs: // https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#feature-flag-events @@ -205,6 +205,30 @@ type FeatureFlagEvent struct { } `json:"object_attributes"` } +// GroupResourceAccessTokenEvent represents a resource access token event for a +// group. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#project-and-group-access-token-events +type GroupResourceAccessTokenEvent struct { + EventName string `json:"event_name"` + ObjectKind string `json:"object_kind"` + GroupID int `json:"group_id"` + Group struct { + GroupID int `json:"group_id"` + GroupName string `json:"group_name"` + GroupPath string `json:"group_path"` + FullPath string `json:"full_path"` + } `json:"group"` + ObjectAttributes struct { + ID int `json:"id"` + UserID int `json:"user_id"` + Name string `json:"name"` + CreatedAt *time.Time `json:"created_at"` + ExpiresAt *ISOTime `json:"expires_at"` + } `json:"object_attributes"` +} + // IssueCommentEvent represents a comment on an issue event. // // GitLab API docs: @@ -737,7 +761,8 @@ type MergeEvent struct { Reviewers []*EventUser `json:"reviewers"` } -// EventUser represents a user record in an event and is used as an even initiator or a merge assignee. +// EventUser represents a user record in an event and is used as an even +// initiator or a merge assignee. type EventUser struct { ID int `json:"id"` Name string `json:"name"` @@ -753,7 +778,8 @@ type MergeParams struct { // UnmarshalJSON decodes the merge parameters // -// This allows support of ForceRemoveSourceBranch for both type bool (>11.9) and string (<11.9) +// This allows support of ForceRemoveSourceBranch for both type +// bool (>11.9) and string (<11.9) func (p *MergeParams) UnmarshalJSON(b []byte) error { type Alias MergeParams raw := struct { @@ -899,6 +925,42 @@ type PipelineEvent struct { } `json:"builds"` } +// ProjectResourceAccessTokenEvent represents a resource access token event for +// a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#project-and-group-access-token-events +type ProjectResourceAccessTokenEvent struct { + EventName string `json:"event_name"` + ObjectKind string `json:"object_kind"` + ProjectID int `json:"project_id"` + Project struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + WebURL string `json:"web_url"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + VisibilityLevel int `json:"visibility_level"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + CIConfigPath string `json:"ci_config_path"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + } `json:"project"` + ObjectAttributes struct { + ID int `json:"id"` + UserID int `json:"user_id"` + Name string `json:"name"` + CreatedAt *time.Time `json:"created_at"` + ExpiresAt *ISOTime `json:"expires_at"` + } `json:"object_attributes"` +} + // PushEvent represents a push event. // // GitLab API docs: diff --git a/event_webhook_types_test.go b/event_webhook_types_test.go index 29a19698f..f31b2fe0b 100644 --- a/event_webhook_types_test.go +++ b/event_webhook_types_test.go @@ -286,6 +286,48 @@ func TestFeatureFlagEventUnmarshal(t *testing.T) { } } +func TestGroupResourceAccessTokenEventUnmarshal(t *testing.T) { + jsonObject := loadFixture("testdata/webhooks/resource_access_token_group.json") + var event *GroupResourceAccessTokenEvent + err := json.Unmarshal(jsonObject, &event) + if err != nil { + t.Errorf("could not unmarshal event: %v\n ", err.Error()) + } + + if event == nil { + t.Errorf("event is null") + } + + createdAt, err := time.Parse(time.RFC3339, "2024-02-05T03:13:44.855Z") + if err != nil { + t.Fatalf("could not parse time: %v", err) + } + + expiresAt, err := ParseISOTime("2024-01-26") + if err != nil { + t.Fatalf("could not parse ISO time: %v", err) + } + + expected := &GroupResourceAccessTokenEvent{ + GroupID: 35, + ObjectKind: "access_token", + EventName: "expiring_access_token", + } + + expected.Group.GroupID = 35 + expected.Group.GroupName = "Twitter" + expected.Group.GroupPath = "twitter" + expected.Group.FullPath = "twitter" + + expected.ObjectAttributes.ID = 25 + expected.ObjectAttributes.UserID = 90 + expected.ObjectAttributes.Name = "acd" + expected.ObjectAttributes.CreatedAt = &createdAt + expected.ObjectAttributes.ExpiresAt = &expiresAt + + assert.Equal(t, expected, event) +} + func TestIssueCommentEventUnmarshal(t *testing.T) { jsonObject := loadFixture("testdata/webhooks/note_issue.json") @@ -1014,6 +1056,60 @@ func TestPipelineEventUnmarshal(t *testing.T) { } } +func TestProjectResourceAccessTokenEventUnmarshal(t *testing.T) { + jsonObject := loadFixture("testdata/webhooks/resource_access_token_project.json") + var event *ProjectResourceAccessTokenEvent + err := json.Unmarshal(jsonObject, &event) + if err != nil { + t.Errorf("could not unmarshal event: %v\n ", err.Error()) + } + + if event == nil { + t.Errorf("event is null") + } + + createdAt, err := time.Parse(time.RFC3339, "2024-02-05T03:13:44.855Z") + if err != nil { + t.Fatalf("could not parse time: %v", err) + } + + expiresAt, err := ParseISOTime("2024-01-26") + if err != nil { + t.Fatalf("could not parse ISO time: %v", err) + } + + expected := &ProjectResourceAccessTokenEvent{ + ProjectID: 7, + ObjectKind: "access_token", + EventName: "expiring_access_token", + } + + expected.ObjectAttributes.ID = 25 + expected.ObjectAttributes.UserID = 90 + expected.ObjectAttributes.Name = "acd" + expected.ObjectAttributes.CreatedAt = &createdAt + expected.ObjectAttributes.ExpiresAt = &expiresAt + + expected.Project.ID = 7 + expected.Project.Name = "Flight" + expected.Project.Description = "Eum dolore maxime atque reprehenderit voluptatem." + expected.Project.WebURL = "https://example.com/flightjs/Flight" + expected.Project.AvatarURL = "" + expected.Project.GitSSHURL = "ssh://git@example.com/flightjs/Flight.git" + expected.Project.GitHTTPURL = "https://example.com/flightjs/Flight.git" + expected.Project.Namespace = "Flightjs" + expected.Project.VisibilityLevel = 0 + expected.Project.PathWithNamespace = "flightjs/Flight" + expected.Project.DefaultBranch = "master" + expected.Project.CIConfigPath = "" + expected.Project.Homepage = "https://example.com/flightjs/Flight" + expected.Project.URL = "ssh://git@example.com/flightjs/Flight.git" + expected.Project.SSHURL = "ssh://git@example.com/flightjs/Flight.git" + expected.Project.HTTPURL = "https://example.com/flightjs/Flight.git" + + assert.Equal(t, expected, event) +} + func TestPushEventUnmarshal(t *testing.T) { jsonObject := loadFixture("testdata/webhooks/push.json") var event *PushEvent diff --git a/testdata/webhooks/resource_access_token_group.json b/testdata/webhooks/resource_access_token_group.json new file mode 100644 index 000000000..613c79506 --- /dev/null +++ b/testdata/webhooks/resource_access_token_group.json @@ -0,0 +1,18 @@ +{ + "object_kind": "access_token", + "group_id": 35, + "group": { + "group_name": "Twitter", + "group_path": "twitter", + "full_path": "twitter", + "group_id": 35 + }, + "object_attributes": { + "user_id": 90, + "created_at": "2024-02-05T03:13:44.855Z", + "id": 25, + "name": "acd", + "expires_at": "2024-01-26" + }, + "event_name": "expiring_access_token" +} diff --git a/testdata/webhooks/resource_access_token_project.json b/testdata/webhooks/resource_access_token_project.json new file mode 100644 index 000000000..efc4dd9fd --- /dev/null +++ b/testdata/webhooks/resource_access_token_project.json @@ -0,0 +1,30 @@ +{ + "object_kind": "access_token", + "project_id": 7, + "project": { + "id": 7, + "name": "Flight", + "description": "Eum dolore maxime atque reprehenderit voluptatem.", + "web_url": "https://example.com/flightjs/Flight", + "avatar_url": null, + "git_ssh_url": "ssh://git@example.com/flightjs/Flight.git", + "git_http_url": "https://example.com/flightjs/Flight.git", + "namespace": "Flightjs", + "visibility_level": 0, + "path_with_namespace": "flightjs/Flight", + "default_branch": "master", + "ci_config_path": null, + "homepage": "https://example.com/flightjs/Flight", + "url": "ssh://git@example.com/flightjs/Flight.git", + "ssh_url": "ssh://git@example.com/flightjs/Flight.git", + "http_url": "https://example.com/flightjs/Flight.git" + }, + "object_attributes": { + "user_id": 90, + "created_at": "2024-02-05T03:13:44.855Z", + "id": 25, + "name": "acd", + "expires_at": "2024-01-26" + }, + "event_name": "expiring_access_token" +}