From 5ac6057667d1ca54b78f8677cde8a481abe50d70 Mon Sep 17 00:00:00 2001 From: Byron Wolfman Date: Mon, 14 Aug 2023 11:58:50 -0300 Subject: [PATCH] Add support for the security and analysis webhook event (#2862) Fixes: #2861. --- github/event.go | 2 + github/event_types.go | 25 ++ github/event_types_test.go | 335 ++++++++++++++++++++++++++ github/github-accessors.go | 64 +++++ github/github-accessors_test.go | 56 +++++ github/messages.go | 1 + github/messages_test.go | 4 + github/repos_hooks_deliveries_test.go | 1 + 8 files changed, 488 insertions(+) diff --git a/github/event.go b/github/event.go index 2349cb77600..b5f39ac7b90 100644 --- a/github/event.go +++ b/github/event.go @@ -133,6 +133,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) { payload = &SecretScanningAlertEvent{} case "SecurityAdvisoryEvent": payload = &SecurityAdvisoryEvent{} + case "SecurityAndAnalysisEvent": + payload = &SecurityAndAnalysisEvent{} case "StarEvent": payload = &StarEvent{} case "StatusEvent": diff --git a/github/event_types.go b/github/event_types.go index a24134479c0..edbe8f53330 100644 --- a/github/event_types.go +++ b/github/event_types.go @@ -1325,6 +1325,31 @@ type SecretScanningAlertEvent struct { Installation *Installation `json:"installation,omitempty"` } +// SecurityAndAnalysisEvent is triggered when code security and analysis features +// are enabled or disabled for a repository. +// +// GitHub API docs: https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#security_and_analysis +type SecurityAndAnalysisEvent struct { + Changes *SecurityAndAnalysisChange `json:"changes,omitempty"` + Enterprise *Enterprise `json:"enterprise,omitempty"` + Installation *Installation `json:"installation,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// SecurityAndAnalysisChange represents the changes when security and analysis +// features are enabled or disabled for a repository. +type SecurityAndAnalysisChange struct { + From *SecurityAndAnalysisChangeFrom `json:"from,omitempty"` +} + +// SecurityAndAnalysisChangeFrom represents which change was made when security +// and analysis features are enabled or disabled for a repository. +type SecurityAndAnalysisChangeFrom struct { + SecurityAndAnalysis *SecurityAndAnalysis `json:"security_and_analysis,omitempty"` +} + // StarEvent is triggered when a star is added or removed from a repository. // The Webhook event name is "star". // diff --git a/github/event_types_test.go b/github/event_types_test.go index 8d6a8b95b16..3703d454af8 100644 --- a/github/event_types_test.go +++ b/github/event_types_test.go @@ -15808,6 +15808,341 @@ func TestSecurityAdvisoryEvent_Marshal(t *testing.T) { testJSONMarshal(t, u, want) } +func TestSecurityAndAnalysisEvent_Marshal(t *testing.T) { + testJSONMarshal(t, &SecurityAndAnalysisEvent{}, "{}") + + u := &SecurityAndAnalysisEvent{ + Changes: &SecurityAndAnalysisChange{ + From: &SecurityAndAnalysisChangeFrom{ + SecurityAndAnalysis: &SecurityAndAnalysis{ + AdvancedSecurity: &AdvancedSecurity{ + Status: String("enabled"), + }, + SecretScanning: &SecretScanning{ + Status: String("enabled"), + }, + SecretScanningPushProtection: &SecretScanningPushProtection{ + Status: String("enabled"), + }, + DependabotSecurityUpdates: &DependabotSecurityUpdates{ + Status: String("enabled"), + }, + }, + }, + }, + Enterprise: &Enterprise{ + ID: Int(1), + Slug: String("s"), + Name: String("n"), + NodeID: String("nid"), + AvatarURL: String("au"), + Description: String("d"), + WebsiteURL: String("wu"), + HTMLURL: String("hu"), + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + }, + Installation: &Installation{ + ID: Int64(1), + NodeID: String("nid"), + AppID: Int64(1), + AppSlug: String("as"), + TargetID: Int64(1), + Account: &User{ + Login: String("l"), + ID: Int64(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + Bio: String("b"), + TwitterUsername: String("t"), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + SuspendedAt: &Timestamp{referenceTime}, + }, + AccessTokensURL: String("atu"), + RepositoriesURL: String("ru"), + HTMLURL: String("hu"), + TargetType: String("tt"), + SingleFileName: String("sfn"), + RepositorySelection: String("rs"), + Events: []string{"e"}, + SingleFilePaths: []string{"s"}, + Permissions: &InstallationPermissions{ + Actions: String("a"), + Administration: String("ad"), + Checks: String("c"), + Contents: String("co"), + ContentReferences: String("cr"), + Deployments: String("d"), + Environments: String("e"), + Issues: String("i"), + Metadata: String("md"), + Members: String("m"), + OrganizationAdministration: String("oa"), + OrganizationHooks: String("oh"), + OrganizationPlan: String("op"), + OrganizationPreReceiveHooks: String("opr"), + OrganizationProjects: String("op"), + OrganizationSecrets: String("os"), + OrganizationSelfHostedRunners: String("osh"), + OrganizationUserBlocking: String("oub"), + Packages: String("pkg"), + Pages: String("pg"), + PullRequests: String("pr"), + RepositoryHooks: String("rh"), + RepositoryProjects: String("rp"), + RepositoryPreReceiveHooks: String("rprh"), + Secrets: String("s"), + SecretScanningAlerts: String("ssa"), + SecurityEvents: String("se"), + SingleFile: String("sf"), + Statuses: String("s"), + TeamDiscussions: String("td"), + VulnerabilityAlerts: String("va"), + Workflows: String("w"), + }, + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + HasMultipleSingleFiles: Bool(false), + SuspendedBy: &User{ + Login: String("l"), + ID: Int64(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + Bio: String("b"), + TwitterUsername: String("t"), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + SuspendedAt: &Timestamp{referenceTime}, + }, + SuspendedAt: &Timestamp{referenceTime}, + }, + Organization: &Organization{ + BillingEmail: String("be"), + Blog: String("b"), + Company: String("c"), + Email: String("e"), + TwitterUsername: String("tu"), + Location: String("loc"), + Name: String("n"), + Description: String("d"), + IsVerified: Bool(true), + HasOrganizationProjects: Bool(true), + HasRepositoryProjects: Bool(true), + DefaultRepoPermission: String("drp"), + MembersCanCreateRepos: Bool(true), + MembersCanCreateInternalRepos: Bool(true), + MembersCanCreatePrivateRepos: Bool(true), + MembersCanCreatePublicRepos: Bool(false), + MembersAllowedRepositoryCreationType: String("marct"), + MembersCanCreatePages: Bool(true), + MembersCanCreatePublicPages: Bool(false), + MembersCanCreatePrivatePages: Bool(true), + }, + Repository: &Repository{ + ID: Int64(1), + URL: String("s"), + Name: String("n"), + }, + Sender: &User{ + Login: String("l"), + ID: Int64(1), + NodeID: String("n"), + URL: String("u"), + ReposURL: String("r"), + EventsURL: String("e"), + AvatarURL: String("a"), + }, + } + + want := `{ + "changes": { + "from": { + "security_and_analysis": { + "advanced_security": { + "status": "enabled" + }, + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "enabled" + } + } + } + }, + "enterprise": { + "id": 1, + "slug": "s", + "name": "n", + "node_id": "nid", + "avatar_url": "au", + "description": "d", + "website_url": "wu", + "html_url": "hu", + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + ` + }, + "installation": { + "id": 1, + "node_id": "nid", + "app_id": 1, + "app_slug": "as", + "target_id": 1, + "account": { + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "bio": "b", + "twitter_username": "t", + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "suspended_at": ` + referenceTimeStr + `, + "url": "u" + }, + "access_tokens_url": "atu", + "repositories_url": "ru", + "html_url": "hu", + "target_type": "tt", + "single_file_name": "sfn", + "repository_selection": "rs", + "events": [ + "e" + ], + "single_file_paths": [ + "s" + ], + "permissions": { + "actions": "a", + "administration": "ad", + "checks": "c", + "contents": "co", + "content_references": "cr", + "deployments": "d", + "environments": "e", + "issues": "i", + "metadata": "md", + "members": "m", + "organization_administration": "oa", + "organization_hooks": "oh", + "organization_plan": "op", + "organization_pre_receive_hooks": "opr", + "organization_projects": "op", + "organization_secrets": "os", + "organization_self_hosted_runners": "osh", + "organization_user_blocking": "oub", + "packages": "pkg", + "pages": "pg", + "pull_requests": "pr", + "repository_hooks": "rh", + "repository_projects": "rp", + "repository_pre_receive_hooks": "rprh", + "secrets": "s", + "secret_scanning_alerts": "ssa", + "security_events": "se", + "single_file": "sf", + "statuses": "s", + "team_discussions": "td", + "vulnerability_alerts": "va", + "workflows": "w" + }, + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + `, + "has_multiple_single_files": false, + "suspended_by": { + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "bio": "b", + "twitter_username": "t", + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "suspended_at": ` + referenceTimeStr + `, + "url": "u" + }, + "suspended_at": ` + referenceTimeStr + ` + }, + "organization": { + "name": "n", + "company": "c", + "blog": "b", + "location": "loc", + "email": "e", + "twitter_username": "tu", + "description": "d", + "billing_email": "be", + "is_verified": true, + "has_organization_projects": true, + "has_repository_projects": true, + "default_repository_permission": "drp", + "members_can_create_repositories": true, + "members_can_create_public_repositories": false, + "members_can_create_private_repositories": true, + "members_can_create_internal_repositories": true, + "members_allowed_repository_creation_type": "marct", + "members_can_create_pages": true, + "members_can_create_public_pages": false, + "members_can_create_private_pages": true + }, + "repository": { + "id": 1, + "url": "s", + "name": "n" + }, + "sender": { + "login": "l", + "id": 1, + "node_id": "n", + "avatar_url": "a", + "url": "u", + "events_url": "e", + "repos_url": "r" + }, + "target_type": "running" + }` + + testJSONMarshal(t, u, want) +} + func TestCodeScanningAlertEvent_Marshal(t *testing.T) { testJSONMarshal(t, &CodeScanningAlertEvent{}, "{}") diff --git a/github/github-accessors.go b/github/github-accessors.go index 3f665ede5cb..b76cc3c0cab 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -20270,6 +20270,70 @@ func (s *SecurityAndAnalysis) GetSecretScanningPushProtection() *SecretScanningP return s.SecretScanningPushProtection } +// GetFrom returns the From field. +func (s *SecurityAndAnalysisChange) GetFrom() *SecurityAndAnalysisChangeFrom { + if s == nil { + return nil + } + return s.From +} + +// GetSecurityAndAnalysis returns the SecurityAndAnalysis field. +func (s *SecurityAndAnalysisChangeFrom) GetSecurityAndAnalysis() *SecurityAndAnalysis { + if s == nil { + return nil + } + return s.SecurityAndAnalysis +} + +// GetChanges returns the Changes field. +func (s *SecurityAndAnalysisEvent) GetChanges() *SecurityAndAnalysisChange { + if s == nil { + return nil + } + return s.Changes +} + +// GetEnterprise returns the Enterprise field. +func (s *SecurityAndAnalysisEvent) GetEnterprise() *Enterprise { + if s == nil { + return nil + } + return s.Enterprise +} + +// GetInstallation returns the Installation field. +func (s *SecurityAndAnalysisEvent) GetInstallation() *Installation { + if s == nil { + return nil + } + return s.Installation +} + +// GetOrganization returns the Organization field. +func (s *SecurityAndAnalysisEvent) GetOrganization() *Organization { + if s == nil { + return nil + } + return s.Organization +} + +// GetRepository returns the Repository field. +func (s *SecurityAndAnalysisEvent) GetRepository() *Repository { + if s == nil { + return nil + } + return s.Repository +} + +// GetSender returns the Sender field. +func (s *SecurityAndAnalysisEvent) GetSender() *User { + if s == nil { + return nil + } + return s.Sender +} + // GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. func (s *SelectedReposList) GetTotalCount() int { if s == nil || s.TotalCount == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 712d8fc94dd..479f3c8d54d 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -23643,6 +23643,62 @@ func TestSecurityAndAnalysis_GetSecretScanningPushProtection(tt *testing.T) { s.GetSecretScanningPushProtection() } +func TestSecurityAndAnalysisChange_GetFrom(tt *testing.T) { + s := &SecurityAndAnalysisChange{} + s.GetFrom() + s = nil + s.GetFrom() +} + +func TestSecurityAndAnalysisChangeFrom_GetSecurityAndAnalysis(tt *testing.T) { + s := &SecurityAndAnalysisChangeFrom{} + s.GetSecurityAndAnalysis() + s = nil + s.GetSecurityAndAnalysis() +} + +func TestSecurityAndAnalysisEvent_GetChanges(tt *testing.T) { + s := &SecurityAndAnalysisEvent{} + s.GetChanges() + s = nil + s.GetChanges() +} + +func TestSecurityAndAnalysisEvent_GetEnterprise(tt *testing.T) { + s := &SecurityAndAnalysisEvent{} + s.GetEnterprise() + s = nil + s.GetEnterprise() +} + +func TestSecurityAndAnalysisEvent_GetInstallation(tt *testing.T) { + s := &SecurityAndAnalysisEvent{} + s.GetInstallation() + s = nil + s.GetInstallation() +} + +func TestSecurityAndAnalysisEvent_GetOrganization(tt *testing.T) { + s := &SecurityAndAnalysisEvent{} + s.GetOrganization() + s = nil + s.GetOrganization() +} + +func TestSecurityAndAnalysisEvent_GetRepository(tt *testing.T) { + s := &SecurityAndAnalysisEvent{} + s.GetRepository() + s = nil + s.GetRepository() +} + +func TestSecurityAndAnalysisEvent_GetSender(tt *testing.T) { + s := &SecurityAndAnalysisEvent{} + s.GetSender() + s = nil + s.GetSender() +} + func TestSelectedReposList_GetTotalCount(tt *testing.T) { var zeroValue int s := &SelectedReposList{TotalCount: &zeroValue} diff --git a/github/messages.go b/github/messages.go index 929587d337f..d62182689c4 100644 --- a/github/messages.go +++ b/github/messages.go @@ -96,6 +96,7 @@ var ( "release": "ReleaseEvent", "secret_scanning_alert": "SecretScanningAlertEvent", "security_advisory": "SecurityAdvisoryEvent", + "security_and_analysis": "SecurityAndAnalysisEvent", "star": "StarEvent", "status": "StatusEvent", "team": "TeamEvent", diff --git a/github/messages_test.go b/github/messages_test.go index 8ee38eb346b..b3828608505 100644 --- a/github/messages_test.go +++ b/github/messages_test.go @@ -452,6 +452,10 @@ func TestParseWebHook(t *testing.T) { payload: &SecurityAdvisoryEvent{}, messageType: "security_advisory", }, + { + payload: &SecurityAndAnalysisEvent{}, + messageType: "security_and_analysis", + }, { payload: &StarEvent{}, messageType: "star", diff --git a/github/repos_hooks_deliveries_test.go b/github/repos_hooks_deliveries_test.go index d1dda365194..a605c7f3654 100644 --- a/github/repos_hooks_deliveries_test.go +++ b/github/repos_hooks_deliveries_test.go @@ -190,6 +190,7 @@ var hookDeliveryPayloadTypeToStruct = map[string]interface{}{ "repository_vulnerability_alert": &RepositoryVulnerabilityAlertEvent{}, "secret_scanning_alert": &SecretScanningAlertEvent{}, "security_advisory": &SecurityAdvisoryEvent{}, + "security_and_analysis": &SecurityAndAnalysisEvent{}, "star": &StarEvent{}, "status": &StatusEvent{}, "team": &TeamEvent{},