From 7ac3a57c1bce44cde331f581c144774e94787632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Tue, 1 Nov 2022 09:57:31 +0100 Subject: [PATCH] Add dependabot alerts --- github/dependabot_alerts.go | 128 +++++++++++++++++++++++++++++ github/dependabot_alerts_test.go | 133 +++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 github/dependabot_alerts.go create mode 100644 github/dependabot_alerts_test.go diff --git a/github/dependabot_alerts.go b/github/dependabot_alerts.go new file mode 100644 index 00000000000..c7507320682 --- /dev/null +++ b/github/dependabot_alerts.go @@ -0,0 +1,128 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +type Dependency struct { + Package VulnerabilityPackage `json:"package"` + ManifestPath string `json:"manifest_path"` + Scope string `json:"scope"` +} + +type AdvisoryCvss struct { + Score float64 `json:"score"` + VectorString string `json:"vector_string"` +} + +type AdvisoryCwes struct { + CweID string `json:"cwe_id"` + Name string `json:"name"` +} + +type DependabotSecurityAdvisory struct { + GHSAID string `json:"ghsa_id"` + CVEID string `json:"cve_id"` + Summary string `json:"summary"` + Description string `json:"description"` + Vulnerabilities []AdvisoryVulnerability `json:"vulnerabilities"` + Severity string `json:"severity"` + Cvss AdvisoryCvss `json:"cvss"` + Cwes []AdvisoryCwes `json:"cwes"` + Identifiers []AdvisoryIdentifier `json:"identifiers"` + References []AdvisoryReference `json:"references"` + PublishedAt Timestamp `json:"published_at"` + UpdatedAt Timestamp `json:"updated_at"` + WithdrawnAt *Timestamp `json:"withdrawn_at"` +} + +// DependabotAlert represents a dependabot alert +type DependabotAlert struct { + Number int `json:"number"` + State string `json:"state"` + Dependency Dependency `json:"dependency"` + SecurityAdvisory DependabotSecurityAdvisory `json:"security_advisory"` + SecurityVulnerability AdvisoryVulnerability `json:"security_vulnerability"` + URL string `json:"url"` + HtmlURL string `json:"html_url"` + CreatedAt Timestamp `json:"created_at"` + UpdatedAt Timestamp `json:"updated_at"` + DismissedAt *Timestamp `json:"dismissed_at"` + DismissedBy *User `json:"dismissed_by"` + DismissedReason string `json:"dismissed_reason"` + DismissedComment string `json:"dismissed_comment"` + FixedAt *Timestamp `json:"fixed_at"` +} + +type ListAlertsOptions struct { + State *string `url:"state,omitempty"` + Severity *string `url:"severity,omitempty"` + Ecosystem *string `url:"ecosystem,omitempty"` + Package *string `url:"package,omitempty"` + Scope *string `url:"scope,omitempty"` + Sort *string `url:"sort,omitempty"` + Direction *string `url:"direction,omitempty"` + + ListCursorOptions +} + +func (s *DependabotService) listAlerts(ctx context.Context, url string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) { + u, err := addOptions(url, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var alerts []*DependabotAlert + resp, err := s.client.Do(ctx, req, &alerts) + if err != nil { + return nil, resp, err + } + + return alerts, resp, nil +} + +// ListRepoAlerts lists all Dependabot alerts of a repository +// +// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-a-repository +func (s *DependabotService) ListRepoAlerts(ctx context.Context, owner, repo string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) { + url := fmt.Sprintf("repos/%v/%v/dependabot/alerts", owner, repo) + return s.listAlerts(ctx, url, opts) +} + +// ListOrgAlerts lists all Dependabot alerts of an organization +// +// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-an-organization +func (s *DependabotService) ListOrgAlerts(ctx context.Context, org string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) { + url := fmt.Sprintf("orgs/%v/dependabot/alerts", org) + return s.listAlerts(ctx, url, opts) +} + +// GetRepoAlert gets a single repository Dependabot alert +// +// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#get-a-dependabot-alert +func (s *DependabotService) GetRepoAlert(ctx context.Context, owner string, repo string, number int) (*DependabotAlert, *Response, error) { + url := fmt.Sprintf("repos/%v/%v/dependabot/alerts/%v", owner, repo, number) + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + alert := new(DependabotAlert) + resp, err := s.client.Do(ctx, req, alert) + if err != nil { + return nil, resp, err + } + + return alert, resp, nil +} diff --git a/github/dependabot_alerts_test.go b/github/dependabot_alerts_test.go new file mode 100644 index 00000000000..f64a1a2762a --- /dev/null +++ b/github/dependabot_alerts_test.go @@ -0,0 +1,133 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestDependabotService_ListRepoAlerts(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/dependabot/alerts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"state": "open"}) + fmt.Fprint(w, `[{"number":1,"state":"open"},{"number":42,"state":"fixed"}]`) + }) + + opts := &ListAlertsOptions{State: String("open")} + ctx := context.Background() + alerts, _, err := client.Dependabot.ListRepoAlerts(ctx, "o", "r", opts) + if err != nil { + t.Errorf("Dependabot.ListRepoAlerts returned error: %v", err) + } + + want := []*DependabotAlert{ + {Number: 1, State: "open"}, + {Number: 42, State: "fixed"}, + } + if !cmp.Equal(alerts, want) { + t.Errorf("Dependabot.ListRepoAlerts returned %+v, want %+v", alerts, want) + } + + const methodName = "ListRepoAlerts" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Dependabot.ListRepoAlerts(ctx, "\n", "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Dependabot.ListRepoAlerts(ctx, "o", "r", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestDependabotService_GetRepoAlert(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/dependabot/alerts/42", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":42,"state":"fixed"}`) + }) + + ctx := context.Background() + alert, _, err := client.Dependabot.GetRepoAlert(ctx, "o", "r", 42) + if err != nil { + t.Errorf("Dependabot.GetRepoAlert returned error: %v", err) + } + + want := &DependabotAlert{ + Number: 42, + State: "fixed", + } + if !cmp.Equal(alert, want) { + t.Errorf("Dependabot.GetRepoAlert returned %+v, want %+v", alert, want) + } + + const methodName = "GetRepoAlert" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Dependabot.GetRepoAlert(ctx, "\n", "\n", 0) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Dependabot.GetRepoAlert(ctx, "o", "r", 42) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestDependabotService_ListOrgAlerts(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/dependabot/alerts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"state": "open"}) + fmt.Fprint(w, `[{"number":1,"state":"open"},{"number":42,"state":"fixed"}]`) + }) + + opts := &ListAlertsOptions{State: String("open")} + ctx := context.Background() + alerts, _, err := client.Dependabot.ListOrgAlerts(ctx, "o", opts) + if err != nil { + t.Errorf("Dependabot.ListOrgAlerts returned error: %v", err) + } + + want := []*DependabotAlert{ + {Number: 1, State: "open"}, + {Number: 42, State: "fixed"}, + } + if !cmp.Equal(alerts, want) { + t.Errorf("Dependabot.ListOrgAlerts returned %+v, want %+v", alerts, want) + } + + const methodName = "ListOrgAlerts" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Dependabot.ListOrgAlerts(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Dependabot.ListOrgAlerts(ctx, "o", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +}