Repository: google/go-github
Pull request #2482 (2022-09-28 20:54:47)

Add support for User SSH signing keys
Introduce support for the new [SSH signing key endpoints](https://docs.github.com/en/rest/users/ssh-signing-keys) introduced into the REST API with the [introduction of SSH commit verification](https://github.blog/changelog/2022-08-23-ssh-commit-verification-now-supported/).


google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/google/go-github/pull/2482/checks?check_run_id=8605180508) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.
gmlewis: @neersighted - please use regular push of commits to this PR and not force push, as it makes it really challenging for the reviewer to see what changes each time.

We always use squash and merge in this repo anyway so it doesn't matter how many commits it takes. Thank you.
codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/2482?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2482](https://codecov.io/gh/google/go-github/pull/2482?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (c3c2c61) into [master](https://codecov.io/gh/google/go-github/commit/0c3cd12a1e6d42b3f14385771a4dca28a2fe6a5f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (0c3cd12) will **decrease** coverage by `0.04%`.
> The diff coverage is `89.47%`.

```diff
@@            Coverage Diff             @@
##           master    #2482      +/-   ##
==========================================
- Coverage   98.03%   97.99%   -0.05%     
==========================================
  Files         122      123       +1     
  Lines       10705    10762      +57     
==========================================
+ Hits        10495    10546      +51     
- Misses        144      148       +4     
- Partials       66       68       +2     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/2482?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/users\_ssh\_signing\_keys.go](https://codecov.io/gh/google/go-github/pull/2482/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL3VzZXJzX3NzaF9zaWduaW5nX2tleXMuZ28=) | `89.47% <89.47%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

neersighted: Fair -- I'm usually used to a "clean history" workflow -- in this case `go generate` was missed.

Patch:
diff --git a/AUTHORS b/AUTHORS
index 80bd26dd..1ee400fe 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -59,6 +59,7 @@ Beyang Liu <beyang.liu@gmail.com>
 Billy Keyes <bluekeyes@gmail.com>
 Billy Lynch <wlynch92@gmail.com>
 Björn Häuser <b.haeuser@rebuy.de>
+Bjorn Neergaard <bjorn@neersighted.com>
 boljen <bol.christophe@gmail.com>
 Brad Harris <bmharris@gmail.com>
 Brad Moylan <moylan.brad@gmail.com>
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 0c87a56e..e410c695 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -86,6 +86,14 @@ func (a *ActionsPermissionsRepository) GetSelectedActionsURL() string {
 	return *a.SelectedActionsURL
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (a *AdminEnforcedChanges) GetFrom() bool {
+	if a == nil || a.From == nil {
+		return false
+	}
+	return *a.From
+}
+
 // GetURL returns the URL field if it's non-nil, zero value otherwise.
 func (a *AdminEnforcement) GetURL() string {
 	if a == nil || a.URL == nil {
@@ -414,6 +422,14 @@ func (a *Alert) GetURL() string {
 	return *a.URL
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (a *AllowDeletionsEnforcementLevelChanges) GetFrom() string {
+	if a == nil || a.From == nil {
+		return ""
+	}
+	return *a.From
+}
+
 // GetRef returns the Ref field if it's non-nil, zero value otherwise.
 func (a *AnalysesListOptions) GetRef() string {
 	if a == nil || a.Ref == nil {
@@ -1366,6 +1382,14 @@ func (a *AuthorizedActorsOnly) GetFrom() bool {
 	return *a.From
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (a *AuthorizedDismissalActorsOnlyChanges) GetFrom() bool {
+	if a == nil || a.From == nil {
+		return false
+	}
+	return *a.From
+}
+
 // GetID returns the ID field if it's non-nil, zero value otherwise.
 func (a *Autolink) GetID() int64 {
 	if a == nil || a.ID == nil {
@@ -3542,6 +3566,14 @@ func (c *CreateOrgInvitationOptions) GetRole() string {
 	return *c.Role
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (c *CreateProtectedChanges) GetFrom() bool {
+	if c == nil || c.From == nil {
+		return false
+	}
+	return *c.From
+}
+
 // GetAllowsPublicRepositories returns the AllowsPublicRepositories field if it's non-nil, zero value otherwise.
 func (c *CreateRunnerGroupRequest) GetAllowsPublicRepositories() bool {
 	if c == nil || c.AllowsPublicRepositories == nil {
@@ -4534,6 +4566,14 @@ func (d *DismissedReview) GetState() string {
 	return *d.State
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (d *DismissStaleReviewsOnPushChanges) GetFrom() bool {
+	if d == nil || d.From == nil {
+		return false
+	}
+	return *d.From
+}
+
 // GetClientPayload returns the ClientPayload field if it's non-nil, zero value otherwise.
 func (d *DispatchRequestOptions) GetClientPayload() json.RawMessage {
 	if d == nil || d.ClientPayload == nil {
@@ -8174,6 +8214,14 @@ func (l *License) GetURL() string {
 	return *l.URL
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (l *LinearHistoryRequirementEnforcementLevelChanges) GetFrom() string {
+	if l == nil || l.From == nil {
+		return ""
+	}
+	return *l.From
+}
+
 // GetAppID returns the AppID field if it's non-nil, zero value otherwise.
 func (l *ListCheckRunsOptions) GetAppID() int64 {
 	if l == nil || l.AppID == nil {
@@ -11806,6 +11854,22 @@ func (p *Protection) GetRestrictions() *BranchRestrictions {
 	return p.Restrictions
 }
 
+// GetAdminEnforced returns the AdminEnforced field.
+func (p *ProtectionChanges) GetAdminEnforced() *AdminEnforcedChanges {
+	if p == nil {
+		return nil
+	}
+	return p.AdminEnforced
+}
+
+// GetAllowDeletionsEnforcementLevel returns the AllowDeletionsEnforcementLevel field.
+func (p *ProtectionChanges) GetAllowDeletionsEnforcementLevel() *AllowDeletionsEnforcementLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.AllowDeletionsEnforcementLevel
+}
+
 // GetAuthorizedActorNames returns the AuthorizedActorNames field.
 func (p *ProtectionChanges) GetAuthorizedActorNames() *AuthorizedActorNames {
 	if p == nil {
@@ -11822,6 +11886,94 @@ func (p *ProtectionChanges) GetAuthorizedActorsOnly() *AuthorizedActorsOnly {
 	return p.AuthorizedActorsOnly
 }
 
+// GetAuthorizedDismissalActorsOnly returns the AuthorizedDismissalActorsOnly field.
+func (p *ProtectionChanges) GetAuthorizedDismissalActorsOnly() *AuthorizedDismissalActorsOnlyChanges {
+	if p == nil {
+		return nil
+	}
+	return p.AuthorizedDismissalActorsOnly
+}
+
+// GetCreateProtected returns the CreateProtected field.
+func (p *ProtectionChanges) GetCreateProtected() *CreateProtectedChanges {
+	if p == nil {
+		return nil
+	}
+	return p.CreateProtected
+}
+
+// GetDismissStaleReviewsOnPush returns the DismissStaleReviewsOnPush field.
+func (p *ProtectionChanges) GetDismissStaleReviewsOnPush() *DismissStaleReviewsOnPushChanges {
+	if p == nil {
+		return nil
+	}
+	return p.DismissStaleReviewsOnPush
+}
+
+// GetLinearHistoryRequirementEnforcementLevel returns the LinearHistoryRequirementEnforcementLevel field.
+func (p *ProtectionChanges) GetLinearHistoryRequirementEnforcementLevel() *LinearHistoryRequirementEnforcementLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.LinearHistoryRequirementEnforcementLevel
+}
+
+// GetPullRequestReviewsEnforcementLevel returns the PullRequestReviewsEnforcementLevel field.
+func (p *ProtectionChanges) GetPullRequestReviewsEnforcementLevel() *PullRequestReviewsEnforcementLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.PullRequestReviewsEnforcementLevel
+}
+
+// GetRequireCodeOwnerReview returns the RequireCodeOwnerReview field.
+func (p *ProtectionChanges) GetRequireCodeOwnerReview() *RequireCodeOwnerReviewChanges {
+	if p == nil {
+		return nil
+	}
+	return p.RequireCodeOwnerReview
+}
+
+// GetRequiredConversationResolutionLevel returns the RequiredConversationResolutionLevel field.
+func (p *ProtectionChanges) GetRequiredConversationResolutionLevel() *RequiredConversationResolutionLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.RequiredConversationResolutionLevel
+}
+
+// GetRequiredDeploymentsEnforcementLevel returns the RequiredDeploymentsEnforcementLevel field.
+func (p *ProtectionChanges) GetRequiredDeploymentsEnforcementLevel() *RequiredDeploymentsEnforcementLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.RequiredDeploymentsEnforcementLevel
+}
+
+// GetRequiredStatusChecks returns the RequiredStatusChecks field.
+func (p *ProtectionChanges) GetRequiredStatusChecks() *RequiredStatusChecksChanges {
+	if p == nil {
+		return nil
+	}
+	return p.RequiredStatusChecks
+}
+
+// GetRequiredStatusChecksEnforcementLevel returns the RequiredStatusChecksEnforcementLevel field.
+func (p *ProtectionChanges) GetRequiredStatusChecksEnforcementLevel() *RequiredStatusChecksEnforcementLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.RequiredStatusChecksEnforcementLevel
+}
+
+// GetSignatureRequirementEnforcementLevel returns the SignatureRequirementEnforcementLevel field.
+func (p *ProtectionChanges) GetSignatureRequirementEnforcementLevel() *SignatureRequirementEnforcementLevelChanges {
+	if p == nil {
+		return nil
+	}
+	return p.SignatureRequirementEnforcementLevel
+}
+
 // GetAllowDeletions returns the AllowDeletions field if it's non-nil, zero value otherwise.
 func (p *ProtectionRequest) GetAllowDeletions() bool {
 	if p == nil || p.AllowDeletions == nil {
@@ -13014,6 +13166,14 @@ func (p *PullRequestReviewsEnforcement) GetDismissalRestrictions() *DismissalRes
 	return p.DismissalRestrictions
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (p *PullRequestReviewsEnforcementLevelChanges) GetFrom() string {
+	if p == nil || p.From == nil {
+		return ""
+	}
+	return *p.From
+}
+
 // GetBypassPullRequestAllowancesRequest returns the BypassPullRequestAllowancesRequest field.
 func (p *PullRequestReviewsEnforcementRequest) GetBypassPullRequestAllowancesRequest() *BypassPullRequestAllowancesRequest {
 	if p == nil {
@@ -16134,6 +16294,30 @@ func (r *RepoStatus) GetURL() string {
 	return *r.URL
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (r *RequireCodeOwnerReviewChanges) GetFrom() bool {
+	if r == nil || r.From == nil {
+		return false
+	}
+	return *r.From
+}
+
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (r *RequiredConversationResolutionLevelChanges) GetFrom() string {
+	if r == nil || r.From == nil {
+		return ""
+	}
+	return *r.From
+}
+
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (r *RequiredDeploymentsEnforcementLevelChanges) GetFrom() string {
+	if r == nil || r.From == nil {
+		return ""
+	}
+	return *r.From
+}
+
 // GetType returns the Type field if it's non-nil, zero value otherwise.
 func (r *RequiredReviewer) GetType() string {
 	if r == nil || r.Type == nil {
@@ -16150,6 +16334,14 @@ func (r *RequiredStatusCheck) GetAppID() int64 {
 	return *r.AppID
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (r *RequiredStatusChecksEnforcementLevelChanges) GetFrom() string {
+	if r == nil || r.From == nil {
+		return ""
+	}
+	return *r.From
+}
+
 // GetStrict returns the Strict field if it's non-nil, zero value otherwise.
 func (r *RequiredStatusChecksRequest) GetStrict() bool {
 	if r == nil || r.Strict == nil {
@@ -17086,6 +17278,14 @@ func (s *ServiceHook) GetName() string {
 	return *s.Name
 }
 
+// GetFrom returns the From field if it's non-nil, zero value otherwise.
+func (s *SignatureRequirementEnforcementLevelChanges) GetFrom() string {
+	if s == nil || s.From == nil {
+		return ""
+	}
+	return *s.From
+}
+
 // GetEnabled returns the Enabled field if it's non-nil, zero value otherwise.
 func (s *SignaturesProtectedBranch) GetEnabled() bool {
 	if s == nil || s.Enabled == nil {
@@ -17230,6 +17430,38 @@ func (s *SourceImportAuthor) GetURL() string {
 	return *s.URL
 }
 
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (s *SSHSigningKey) GetCreatedAt() Timestamp {
+	if s == nil || s.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *s.CreatedAt
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (s *SSHSigningKey) GetID() int64 {
+	if s == nil || s.ID == nil {
+		return 0
+	}
+	return *s.ID
+}
+
+// GetKey returns the Key field if it's non-nil, zero value otherwise.
+func (s *SSHSigningKey) GetKey() string {
+	if s == nil || s.Key == nil {
+		return ""
+	}
+	return *s.Key
+}
+
+// GetTitle returns the Title field if it's non-nil, zero value otherwise.
+func (s *SSHSigningKey) GetTitle() string {
+	if s == nil || s.Title == nil {
+		return ""
+	}
+	return *s.Title
+}
+
 // GetAction returns the Action field if it's non-nil, zero value otherwise.
 func (s *StarEvent) GetAction() string {
 	if s == nil || s.Action == nil {
diff --git a/github/repos.go b/github/repos.go
index 309e4430..8f5bbea9 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -872,8 +872,31 @@ type BranchProtectionRule struct {
 
 // ProtectionChanges represents the changes to the rule if the BranchProtection was edited.
 type ProtectionChanges struct {
-	AuthorizedActorsOnly *AuthorizedActorsOnly `json:"authorized_actors_only,omitempty"`
-	AuthorizedActorNames *AuthorizedActorNames `json:"authorized_actor_names,omitempty"`
+	AdminEnforced                            *AdminEnforcedChanges                            `json:"admin_enforced,omitempty"`
+	AllowDeletionsEnforcementLevel           *AllowDeletionsEnforcementLevelChanges           `json:"allow_deletions_enforcement_level,omitempty"`
+	AuthorizedActorNames                     *AuthorizedActorNames                            `json:"authorized_actor_names,omitempty"`
+	AuthorizedActorsOnly                     *AuthorizedActorsOnly                            `json:"authorized_actors_only,omitempty"`
+	AuthorizedDismissalActorsOnly            *AuthorizedDismissalActorsOnlyChanges            `json:"authorized_dismissal_actors_only,omitempty"`
+	CreateProtected                          *CreateProtectedChanges                          `json:"create_protected,omitempty"`
+	DismissStaleReviewsOnPush                *DismissStaleReviewsOnPushChanges                `json:"dismiss_stale_reviews_on_push,omitempty"`
+	LinearHistoryRequirementEnforcementLevel *LinearHistoryRequirementEnforcementLevelChanges `json:"linear_history_requirement_enforcement_level,omitempty"`
+	PullRequestReviewsEnforcementLevel       *PullRequestReviewsEnforcementLevelChanges       `json:"pull_request_reviews_enforcement_level,omitempty"`
+	RequireCodeOwnerReview                   *RequireCodeOwnerReviewChanges                   `json:"require_code_owner_review,omitempty"`
+	RequiredConversationResolutionLevel      *RequiredConversationResolutionLevelChanges      `json:"required_conversation_resolution_level,omitempty"`
+	RequiredDeploymentsEnforcementLevel      *RequiredDeploymentsEnforcementLevelChanges      `json:"required_deployments_enforcement_level,omitempty"`
+	RequiredStatusChecks                     *RequiredStatusChecksChanges                     `json:"required_status_checks,omitempty"`
+	RequiredStatusChecksEnforcementLevel     *RequiredStatusChecksEnforcementLevelChanges     `json:"required_status_checks_enforcement_level,omitempty"`
+	SignatureRequirementEnforcementLevel     *SignatureRequirementEnforcementLevelChanges     `json:"signature_requirement_enforcement_level,omitempty"`
+}
+
+// AdminEnforcedChanges represents the changes made to the AdminEnforced policy.
+type AdminEnforcedChanges struct {
+	From *bool `json:"from,omitempty"`
+}
+
+// AllowDeletionsEnforcementLevelChanges represents the changes made to the AllowDeletionsEnforcementLevel policy.
+type AllowDeletionsEnforcementLevelChanges struct {
+	From *string `json:"from,omitempty"`
 }
 
 // AuthorizedActorNames represents who are authorized to edit the branch protection rules.
@@ -881,11 +904,66 @@ type AuthorizedActorNames struct {
 	From []string `json:"from,omitempty"`
 }
 
-// AuthorizedActorsOnly represents if the branche rule can be edited by authorized actors only.
+// AuthorizedActorsOnly represents if the branch rule can be edited by authorized actors only.
 type AuthorizedActorsOnly struct {
 	From *bool `json:"from,omitempty"`
 }
 
+// AuthorizedDismissalActorsOnlyChanges represents the changes made to the AuthorizedDismissalActorsOnly policy.
+type AuthorizedDismissalActorsOnlyChanges struct {
+	From *bool `json:"from,omitempty"`
+}
+
+// CreateProtectedChanges represents the changes made to the CreateProtected policy.
+type CreateProtectedChanges struct {
+	From *bool `json:"from,omitempty"`
+}
+
+// DismissStaleReviewsOnPushChanges represents the changes made to the DismissStaleReviewsOnPushChanges policy.
+type DismissStaleReviewsOnPushChanges struct {
+	From *bool `json:"from,omitempty"`
+}
+
+// LinearHistoryRequirementEnforcementLevelChanges represents the changes made to the LinearHistoryRequirementEnforcementLevel policy.
+type LinearHistoryRequirementEnforcementLevelChanges struct {
+	From *string `json:"from,omitempty"`
+}
+
+// PullRequestReviewsEnforcementLevelChanges represents the changes made to the PullRequestReviewsEnforcementLevel policy.
+type PullRequestReviewsEnforcementLevelChanges struct {
+	From *string `json:"from,omitempty"`
+}
+
+// RequireCodeOwnerReviewChanges represents the changes made to the RequireCodeOwnerReview policy.
+type RequireCodeOwnerReviewChanges struct {
+	From *bool `json:"from,omitempty"`
+}
+
+// RequiredConversationResolutionLevelChanges represents the changes made to the RequiredConversationResolutionLevel policy.
+type RequiredConversationResolutionLevelChanges struct {
+	From *string `json:"from,omitempty"`
+}
+
+// RequiredDeploymentsEnforcementLevelChanges represents the changes made to the RequiredDeploymentsEnforcementLevel policy.
+type RequiredDeploymentsEnforcementLevelChanges struct {
+	From *string `json:"from,omitempty"`
+}
+
+// RequiredStatusChecksChanges represents the changes made to the RequiredStatusChecks policy.
+type RequiredStatusChecksChanges struct {
+	From []string `json:"from,omitempty"`
+}
+
+// RequiredStatusChecksEnforcementLevelChanges represents the changes made to the RequiredStatusChecksEnforcementLevel policy.
+type RequiredStatusChecksEnforcementLevelChanges struct {
+	From *string `json:"from,omitempty"`
+}
+
+// SignatureRequirementEnforcementLevelChanges represents the changes made to the SignatureRequirementEnforcementLevel policy.
+type SignatureRequirementEnforcementLevelChanges struct {
+	From *string `json:"from,omitempty"`
+}
+
 // ProtectionRequest represents a request to create/edit a branch's protection.
 type ProtectionRequest struct {
 	RequiredStatusChecks       *RequiredStatusChecks                 `json:"required_status_checks"`
diff --git a/github/users_ssh_signing_keys.go b/github/users_ssh_signing_keys.go
new file mode 100644
index 00000000..567623f8
--- /dev/null
+++ b/github/users_ssh_signing_keys.go
@@ -0,0 +1,108 @@
+// 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"
+)
+
+// SSHSigningKey represents a public SSH key used to sign git commits.
+type SSHSigningKey struct {
+	ID        *int64     `json:"id,omitempty"`
+	Key       *string    `json:"key,omitempty"`
+	Title     *string    `json:"title,omitempty"`
+	CreatedAt *Timestamp `json:"created_at,omitempty"`
+}
+
+func (k SSHSigningKey) String() string {
+	return Stringify(k)
+}
+
+// ListSSHSigningKeys lists the SSH signing keys for a user. Passing an empty
+// username string will fetch SSH signing keys for the authenticated user.
+//
+// GitHub API docs: https://docs.github.com/en/rest/users/ssh-signing-keys#list-ssh-signing-keys-for-the-authenticated-user
+// GitHub API docs: https://docs.github.com/en/rest/users/ssh-signing-keys#list-ssh-signing-keys-for-a-user
+func (s *UsersService) ListSSHSigningKeys(ctx context.Context, user string, opts *ListOptions) ([]*SSHSigningKey, *Response, error) {
+	var u string
+	if user != "" {
+		u = fmt.Sprintf("users/%v/ssh_signing_keys", user)
+	} else {
+		u = "user/ssh_signing_keys"
+	}
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var keys []*SSHSigningKey
+	resp, err := s.client.Do(ctx, req, &keys)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return keys, resp, nil
+}
+
+// GetSSHSigningKey fetches a single SSH signing key for the authenticated user.
+//
+// GitHub API docs: https://docs.github.com/en/rest/users/ssh-signing-keys#get-an-ssh-signing-key-for-the-authenticated-user
+func (s *UsersService) GetSSHSigningKey(ctx context.Context, id int64) (*SSHSigningKey, *Response, error) {
+	u := fmt.Sprintf("user/ssh_signing_keys/%v", id)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	key := new(SSHSigningKey)
+	resp, err := s.client.Do(ctx, req, key)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return key, resp, nil
+}
+
+// CreateSSHSigningKey adds a SSH signing key for the authenticated user.
+//
+// GitHub API docs: https://docs.github.com/en/rest/users/ssh-signing-keys#create-a-ssh-signing-key-for-the-authenticated-user
+func (s *UsersService) CreateSSHSigningKey(ctx context.Context, key *Key) (*SSHSigningKey, *Response, error) {
+	u := "user/ssh_signing_keys"
+
+	req, err := s.client.NewRequest("POST", u, key)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(SSHSigningKey)
+	resp, err := s.client.Do(ctx, req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, nil
+}
+
+// DeleteKey deletes a SSH signing key for the authenticated user.
+//
+// GitHub API docs: https://docs.github.com/en/rest/users/ssh-signing-keys#delete-an-ssh-signing-key-for-the-authenticated-user
+func (s *UsersService) DeleteSSHSigningKey(ctx context.Context, id int64) (*Response, error) {
+	u := fmt.Sprintf("user/ssh_signing_keys/%v", id)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/scrape/apps.go b/scrape/apps.go
index e8d810db..0ac16c75 100644
--- a/scrape/apps.go
+++ b/scrape/apps.go
@@ -28,7 +28,7 @@ func (c *Client) AppRestrictionsEnabled(org string) (bool, error) {
 		return false, err
 	}
 
-	s := doc.Find(".oauth-application-whitelist svg").First()
+	s := doc.Find(".oauth-application-allowlist svg").First()
 	if s.Length() == 0 {
 		return false, errors.New("unable to find expected markup")
 	}
@@ -52,7 +52,7 @@ func (c *Client) ListOAuthApps(org string) ([]OAuthApp, error) {
 	}
 
 	var apps []OAuthApp
-	doc.Find(".oauth-application-whitelist ul > li").Each(func(i int, s *goquery.Selection) {
+	doc.Find(".oauth-application-allowlist ul > li").Each(func(i int, s *goquery.Selection) {
 		var app OAuthApp
 		app.Name = s.Find(".request-info strong").First().Text()
 		app.Description = s.Find(".request-info .application-description").Text()


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c528b950..f392c707 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -36,7 +36,7 @@ jobs:
 
     steps:
     - name: Cancel previous
-      uses: styfle/cancel-workflow-action@bb6001c4ea612bf59c3abfc4756fbceee4f870c7 #0.10.0
+      uses: styfle/cancel-workflow-action@9f10b1b9fa56e99e4c5d12c2a085c8a0c37ab0ac #0.10.1
       with:
         access_token: ${{ github.token }}
 
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 086db726..506db423 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -105,6 +105,16 @@ func TestActionsPermissionsRepository_GetSelectedActionsURL(tt *testing.T) {
 	a.GetSelectedActionsURL()
 }
 
+func TestAdminEnforcedChanges_GetFrom(tt *testing.T) {
+	var zeroValue bool
+	a := &AdminEnforcedChanges{From: &zeroValue}
+	a.GetFrom()
+	a = &AdminEnforcedChanges{}
+	a.GetFrom()
+	a = nil
+	a.GetFrom()
+}
+
 func TestAdminEnforcement_GetURL(tt *testing.T) {
 	var zeroValue string
 	a := &AdminEnforcement{URL: &zeroValue}
@@ -461,6 +471,16 @@ func TestAlert_GetURL(tt *testing.T) {
 	a.GetURL()
 }
 
+func TestAllowDeletionsEnforcementLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	a := &AllowDeletionsEnforcementLevelChanges{From: &zeroValue}
+	a.GetFrom()
+	a = &AllowDeletionsEnforcementLevelChanges{}
+	a.GetFrom()
+	a = nil
+	a.GetFrom()
+}
+
 func TestAnalysesListOptions_GetRef(tt *testing.T) {
 	var zeroValue string
 	a := &AnalysesListOptions{Ref: &zeroValue}
@@ -1630,6 +1650,16 @@ func TestAuthorizedActorsOnly_GetFrom(tt *testing.T) {
 	a.GetFrom()
 }
 
+func TestAuthorizedDismissalActorsOnlyChanges_GetFrom(tt *testing.T) {
+	var zeroValue bool
+	a := &AuthorizedDismissalActorsOnlyChanges{From: &zeroValue}
+	a.GetFrom()
+	a = &AuthorizedDismissalActorsOnlyChanges{}
+	a.GetFrom()
+	a = nil
+	a.GetFrom()
+}
+
 func TestAutolink_GetID(tt *testing.T) {
 	var zeroValue int64
 	a := &Autolink{ID: &zeroValue}
@@ -4143,6 +4173,16 @@ func TestCreateOrgInvitationOptions_GetRole(tt *testing.T) {
 	c.GetRole()
 }
 
+func TestCreateProtectedChanges_GetFrom(tt *testing.T) {
+	var zeroValue bool
+	c := &CreateProtectedChanges{From: &zeroValue}
+	c.GetFrom()
+	c = &CreateProtectedChanges{}
+	c.GetFrom()
+	c = nil
+	c.GetFrom()
+}
+
 func TestCreateRunnerGroupRequest_GetAllowsPublicRepositories(tt *testing.T) {
 	var zeroValue bool
 	c := &CreateRunnerGroupRequest{AllowsPublicRepositories: &zeroValue}
@@ -5296,6 +5336,16 @@ func TestDismissedReview_GetState(tt *testing.T) {
 	d.GetState()
 }
 
+func TestDismissStaleReviewsOnPushChanges_GetFrom(tt *testing.T) {
+	var zeroValue bool
+	d := &DismissStaleReviewsOnPushChanges{From: &zeroValue}
+	d.GetFrom()
+	d = &DismissStaleReviewsOnPushChanges{}
+	d.GetFrom()
+	d = nil
+	d.GetFrom()
+}
+
 func TestDispatchRequestOptions_GetClientPayload(tt *testing.T) {
 	var zeroValue json.RawMessage
 	d := &DispatchRequestOptions{ClientPayload: &zeroValue}
@@ -9576,6 +9626,16 @@ func TestLicense_GetURL(tt *testing.T) {
 	l.GetURL()
 }
 
+func TestLinearHistoryRequirementEnforcementLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	l := &LinearHistoryRequirementEnforcementLevelChanges{From: &zeroValue}
+	l.GetFrom()
+	l = &LinearHistoryRequirementEnforcementLevelChanges{}
+	l.GetFrom()
+	l = nil
+	l.GetFrom()
+}
+
 func TestListCheckRunsOptions_GetAppID(tt *testing.T) {
 	var zeroValue int64
 	l := &ListCheckRunsOptions{AppID: &zeroValue}
@@ -13771,6 +13831,20 @@ func TestProtection_GetRestrictions(tt *testing.T) {
 	p.GetRestrictions()
 }
 
+func TestProtectionChanges_GetAdminEnforced(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetAdminEnforced()
+	p = nil
+	p.GetAdminEnforced()
+}
+
+func TestProtectionChanges_GetAllowDeletionsEnforcementLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetAllowDeletionsEnforcementLevel()
+	p = nil
+	p.GetAllowDeletionsEnforcementLevel()
+}
+
 func TestProtectionChanges_GetAuthorizedActorNames(tt *testing.T) {
 	p := &ProtectionChanges{}
 	p.GetAuthorizedActorNames()
@@ -13785,6 +13859,83 @@ func TestProtectionChanges_GetAuthorizedActorsOnly(tt *testing.T) {
 	p.GetAuthorizedActorsOnly()
 }
 
+func TestProtectionChanges_GetAuthorizedDismissalActorsOnly(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetAuthorizedDismissalActorsOnly()
+	p = nil
+	p.GetAuthorizedDismissalActorsOnly()
+}
+
+func TestProtectionChanges_GetCreateProtected(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetCreateProtected()
+	p = nil
+	p.GetCreateProtected()
+}
+
+func TestProtectionChanges_GetDismissStaleReviewsOnPush(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetDismissStaleReviewsOnPush()
+	p = nil
+	p.GetDismissStaleReviewsOnPush()
+}
+
+func TestProtectionChanges_GetLinearHistoryRequirementEnforcementLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetLinearHistoryRequirementEnforcementLevel()
+	p = nil
+	p.GetLinearHistoryRequirementEnforcementLevel()
+}
+
+func TestProtectionChanges_GetPullRequestReviewsEnforcementLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetPullRequestReviewsEnforcementLevel()
+	p = nil
+	p.GetPullRequestReviewsEnforcementLevel()
+}
+
+func TestProtectionChanges_GetRequireCodeOwnerReview(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetRequireCodeOwnerReview()
+	p = nil
+	p.GetRequireCodeOwnerReview()
+}
+
+func TestProtectionChanges_GetRequiredConversationResolutionLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetRequiredConversationResolutionLevel()
+	p = nil
+	p.GetRequiredConversationResolutionLevel()
+}
+
+func TestProtectionChanges_GetRequiredDeploymentsEnforcementLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetRequiredDeploymentsEnforcementLevel()
+	p = nil
+	p.GetRequiredDeploymentsEnforcementLevel()
+}
+
+func TestProtectionChanges_GetRequiredStatusChecks(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetRequiredStatusChecks()
+	p = nil
+	p.GetRequiredStatusChecks()
+}
+
+func TestProtectionChanges_GetRequiredStatusChecksEnforcementLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetRequiredStatusChecksEnforcementLevel()
+	p = nil
+	p.GetRequiredStatusChecksEnforcementLevel()
+}
+
+func TestProtectionChanges_GetSignatureRequirementEnforcementLevel(tt *testing.T) {
+	p := &ProtectionChanges{}
+	p.GetSignatureRequirementEnforcementLevel()
+	p = nil
+	p.GetSignatureRequirementEnforcementLevel()
+}
+
 func TestProtectionRequest_GetAllowDeletions(tt *testing.T) {
 	var zeroValue bool
 	p := &ProtectionRequest{AllowDeletions: &zeroValue}
@@ -15143,6 +15294,16 @@ func TestPullRequestReviewsEnforcement_GetDismissalRestrictions(tt *testing.T) {
 	p.GetDismissalRestrictions()
 }
 
+func TestPullRequestReviewsEnforcementLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	p := &PullRequestReviewsEnforcementLevelChanges{From: &zeroValue}
+	p.GetFrom()
+	p = &PullRequestReviewsEnforcementLevelChanges{}
+	p.GetFrom()
+	p = nil
+	p.GetFrom()
+}
+
 func TestPullRequestReviewsEnforcementRequest_GetBypassPullRequestAllowancesRequest(tt *testing.T) {
 	p := &PullRequestReviewsEnforcementRequest{}
 	p.GetBypassPullRequestAllowancesRequest()
@@ -18794,6 +18955,36 @@ func TestRepoStatus_GetURL(tt *testing.T) {
 	r.GetURL()
 }
 
+func TestRequireCodeOwnerReviewChanges_GetFrom(tt *testing.T) {
+	var zeroValue bool
+	r := &RequireCodeOwnerReviewChanges{From: &zeroValue}
+	r.GetFrom()
+	r = &RequireCodeOwnerReviewChanges{}
+	r.GetFrom()
+	r = nil
+	r.GetFrom()
+}
+
+func TestRequiredConversationResolutionLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	r := &RequiredConversationResolutionLevelChanges{From: &zeroValue}
+	r.GetFrom()
+	r = &RequiredConversationResolutionLevelChanges{}
+	r.GetFrom()
+	r = nil
+	r.GetFrom()
+}
+
+func TestRequiredDeploymentsEnforcementLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	r := &RequiredDeploymentsEnforcementLevelChanges{From: &zeroValue}
+	r.GetFrom()
+	r = &RequiredDeploymentsEnforcementLevelChanges{}
+	r.GetFrom()
+	r = nil
+	r.GetFrom()
+}
+
 func TestRequiredReviewer_GetType(tt *testing.T) {
 	var zeroValue string
 	r := &RequiredReviewer{Type: &zeroValue}
@@ -18814,6 +19005,16 @@ func TestRequiredStatusCheck_GetAppID(tt *testing.T) {
 	r.GetAppID()
 }
 
+func TestRequiredStatusChecksEnforcementLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	r := &RequiredStatusChecksEnforcementLevelChanges{From: &zeroValue}
+	r.GetFrom()
+	r = &RequiredStatusChecksEnforcementLevelChanges{}
+	r.GetFrom()
+	r = nil
+	r.GetFrom()
+}
+
 func TestRequiredStatusChecksRequest_GetStrict(tt *testing.T) {
 	var zeroValue bool
 	r := &RequiredStatusChecksRequest{Strict: &zeroValue}
@@ -19942,6 +20143,16 @@ func TestServiceHook_GetName(tt *testing.T) {
 	s.GetName()
 }
 
+func TestSignatureRequirementEnforcementLevelChanges_GetFrom(tt *testing.T) {
+	var zeroValue string
+	s := &SignatureRequirementEnforcementLevelChanges{From: &zeroValue}
+	s.GetFrom()
+	s = &SignatureRequirementEnforcementLevelChanges{}
+	s.GetFrom()
+	s = nil
+	s.GetFrom()
+}
+
 func TestSignaturesProtectedBranch_GetEnabled(tt *testing.T) {
 	var zeroValue bool
 	s := &SignaturesProtectedBranch{Enabled: &zeroValue}
@@ -20116,6 +20327,46 @@ func TestSourceImportAuthor_GetURL(tt *testing.T) {
 	s.GetURL()
 }
 
+func TestSSHSigningKey_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	s := &SSHSigningKey{CreatedAt: &zeroValue}
+	s.GetCreatedAt()
+	s = &SSHSigningKey{}
+	s.GetCreatedAt()
+	s = nil
+	s.GetCreatedAt()
+}
+
+func TestSSHSigningKey_GetID(tt *testing.T) {
+	var zeroValue int64
+	s := &SSHSigningKey{ID: &zeroValue}
+	s.GetID()
+	s = &SSHSigningKey{}
+	s.GetID()
+	s = nil
+	s.GetID()
+}
+
+func TestSSHSigningKey_GetKey(tt *testing.T) {
+	var zeroValue string
+	s := &SSHSigningKey{Key: &zeroValue}
+	s.GetKey()
+	s = &SSHSigningKey{}
+	s.GetKey()
+	s = nil
+	s.GetKey()
+}
+
+func TestSSHSigningKey_GetTitle(tt *testing.T) {
+	var zeroValue string
+	s := &SSHSigningKey{Title: &zeroValue}
+	s.GetTitle()
+	s = &SSHSigningKey{}
+	s.GetTitle()
+	s = nil
+	s.GetTitle()
+}
+
 func TestStarEvent_GetAction(tt *testing.T) {
 	var zeroValue string
 	s := &StarEvent{Action: &zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index 09a6888e..b83ff237 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -1714,6 +1714,19 @@ func TestRepositoryRelease_String(t *testing.T) {
 	}
 }
 
+func TestSSHSigningKey_String(t *testing.T) {
+	v := SSHSigningKey{
+		ID:        Int64(0),
+		Key:       String(""),
+		Title:     String(""),
+		CreatedAt: &Timestamp{},
+	}
+	want := `github.SSHSigningKey{ID:0, Key:"", Title:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
+	if got := v.String(); got != want {
+		t.Errorf("SSHSigningKey.String = %v, want %v", got, want)
+	}
+}
+
 func TestSecretScanning_String(t *testing.T) {
 	v := SecretScanning{
 		Status: String(""),
diff --git a/github/users_keys_test.go b/github/users_keys_test.go
index b3e5fc9e..ac5c10d0 100644
--- a/github/users_keys_test.go
+++ b/github/users_keys_test.go
@@ -138,12 +138,12 @@ func TestUsersService_CreateKey(t *testing.T) {
 	ctx := context.Background()
 	key, _, err := client.Users.CreateKey(ctx, input)
 	if err != nil {
-		t.Errorf("Users.GetKey returned error: %v", err)
+		t.Errorf("Users.CreateKey returned error: %v", err)
 	}
 
 	want := &Key{ID: Int64(1)}
 	if !cmp.Equal(key, want) {
-		t.Errorf("Users.GetKey returned %+v, want %+v", key, want)
+		t.Errorf("Users.CreateKey returned %+v, want %+v", key, want)
 	}
 
 	const methodName = "CreateKey"
diff --git a/github/users_ssh_signing_keys_test.go b/github/users_ssh_signing_keys_test.go
new file mode 100644
index 00000000..3bf59e10
--- /dev/null
+++ b/github/users_ssh_signing_keys_test.go
@@ -0,0 +1,202 @@
+// 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"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestUsersService_ListSSHSigningKeys_authenticatedUser(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/user/ssh_signing_keys", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"page": "2"})
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 2}
+	ctx := context.Background()
+	keys, _, err := client.Users.ListSSHSigningKeys(ctx, "", opt)
+	if err != nil {
+		t.Errorf("Users.ListSSHSigningKeys returned error: %v", err)
+	}
+
+	want := []*SSHSigningKey{{ID: Int64(1)}}
+	if !cmp.Equal(keys, want) {
+		t.Errorf("Users.ListSSHSigningKeys returned %+v, want %+v", keys, want)
+	}
+
+	const methodName = "ListSSHSigningKeys"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Users.ListSSHSigningKeys(ctx, "\n", opt)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Users.ListSSHSigningKeys(ctx, "", opt)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestUsersService_ListSSHSigningKeys_specifiedUser(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/users/u/ssh_signing_keys", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	ctx := context.Background()
+	keys, _, err := client.Users.ListSSHSigningKeys(ctx, "u", nil)
+	if err != nil {
+		t.Errorf("Users.ListSSHSigningKeys returned error: %v", err)
+	}
+
+	want := []*SSHSigningKey{{ID: Int64(1)}}
+	if !cmp.Equal(keys, want) {
+		t.Errorf("Users.ListSSHSigningKeys returned %+v, want %+v", keys, want)
+	}
+}
+
+func TestUsersService_ListSSHSigningKeys_invalidUser(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	ctx := context.Background()
+	_, _, err := client.Users.ListSSHSigningKeys(ctx, "%", nil)
+	testURLParseError(t, err)
+}
+
+func TestUsersService_GetSSHSigningKey(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/user/ssh_signing_keys/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"id":1}`)
+	})
+
+	ctx := context.Background()
+	key, _, err := client.Users.GetSSHSigningKey(ctx, 1)
+	if err != nil {
+		t.Errorf("Users.GetSSHSigningKey returned error: %v", err)
+	}
+
+	want := &SSHSigningKey{ID: Int64(1)}
+	if !cmp.Equal(key, want) {
+		t.Errorf("Users.GetSSHSigningKey returned %+v, want %+v", key, want)
+	}
+
+	const methodName = "GetSSHSigningKey"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Users.GetSSHSigningKey(ctx, -1)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Users.GetSSHSigningKey(ctx, 1)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestUsersService_CreateSSHSigningKey(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	input := &Key{Key: String("k"), Title: String("t")}
+
+	mux.HandleFunc("/user/ssh_signing_keys", func(w http.ResponseWriter, r *http.Request) {
+		v := new(Key)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, "POST")
+		if !cmp.Equal(v, input) {
+			t.Errorf("Request body = %+v, want %+v", v, input)
+		}
+
+		fmt.Fprint(w, `{"id":1}`)
+	})
+
+	ctx := context.Background()
+	key, _, err := client.Users.CreateSSHSigningKey(ctx, input)
+	if err != nil {
+		t.Errorf("Users.CreateSSHSigningKey returned error: %v", err)
+	}
+
+	want := &SSHSigningKey{ID: Int64(1)}
+	if !cmp.Equal(key, want) {
+		t.Errorf("Users.CreateSSHSigningKey returned %+v, want %+v", key, want)
+	}
+
+	const methodName = "CreateKey"
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Users.CreateKey(ctx, input)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestUsersService_DeleteSSHSigningKey(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/user/ssh_signing_keys/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	ctx := context.Background()
+	_, err := client.Users.DeleteSSHSigningKey(ctx, 1)
+	if err != nil {
+		t.Errorf("Users.DeleteSSHSigningKey returned error: %v", err)
+	}
+
+	const methodName = "DeleteSSHSigningKey"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Users.DeleteSSHSigningKey(ctx, -1)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Users.DeleteSSHSigningKey(ctx, 1)
+	})
+}
+
+func TestSSHSigningKey_Marshal(t *testing.T) {
+	testJSONMarshal(t, &SSHSigningKey{}, "{}")
+
+	u := &Key{
+		ID:        Int64(1),
+		Key:       String("abc"),
+		Title:     String("title"),
+		CreatedAt: &Timestamp{referenceTime},
+	}
+
+	want := `{
+		"id": 1,
+		"key": "abc",
+		"title": "title",
+		"created_at": ` + referenceTimeStr + `
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/scrape/testdata/access-restrictions-disabled.html b/scrape/testdata/access-restrictions-disabled.html
index a5fbe34c..e5431e14 100644
--- a/scrape/testdata/access-restrictions-disabled.html
+++ b/scrape/testdata/access-restrictions-disabled.html
@@ -12,7 +12,7 @@
 
     <div class="col-9">
 
-  <div class="boxed-group oauth-application-whitelist ">
+  <div class="boxed-group oauth-application-allowlist ">
     <h3>Third-party application access policy</h3>
     <div class="boxed-group-inner">
         <p>
diff --git a/scrape/testdata/access-restrictions-enabled.html b/scrape/testdata/access-restrictions-enabled.html
index 4b8d1db8..d5b869c7 100644
--- a/scrape/testdata/access-restrictions-enabled.html
+++ b/scrape/testdata/access-restrictions-enabled.html
@@ -14,7 +14,7 @@
 
     <div class="col-9">
 
-  <div class="boxed-group oauth-application-whitelist is-selectable">
+  <div class="boxed-group oauth-application-allowlist is-selectable">
     <h3>Third-party application access policy</h3>
     <div class="boxed-group-inner">
         <p>