From 0dcd9f3e395fe2c32f6176d1f3987405de7da08e Mon Sep 17 00:00:00 2001 From: Bjorn Neergaard Date: Wed, 28 Sep 2022 14:53:13 -0600 Subject: [PATCH] Add support for User SSH signing keys --- AUTHORS | 1 + github/users_keys_test.go | 4 +- github/users_ssh_signing_keys.go | 108 ++++++++++++++ github/users_ssh_signing_keys_test.go | 202 ++++++++++++++++++++++++++ 4 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 github/users_ssh_signing_keys.go create mode 100644 github/users_ssh_signing_keys_test.go diff --git a/AUTHORS b/AUTHORS index 80bd26dd4f5..1ee400fe206 100644 --- a/AUTHORS +++ b/AUTHORS @@ -59,6 +59,7 @@ Beyang Liu Billy Keyes Billy Lynch Björn Häuser +Bjorn Neergaard boljen Brad Harris Brad Moylan diff --git a/github/users_keys_test.go b/github/users_keys_test.go index b3e5fc9e7a3..9c73bb100b5 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.CreateGetKey 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.CreateGetKey returned %+v, want %+v", key, want) } const methodName = "CreateKey" diff --git a/github/users_ssh_signing_keys.go b/github/users_ssh_signing_keys.go new file mode 100644 index 00000000000..567623f8875 --- /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/github/users_ssh_signing_keys_test.go b/github/users_ssh_signing_keys_test.go new file mode 100644 index 00000000000..3bf59e108db --- /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) +}