From 8d9280e36a846bf6f02be1a63314190faec87786 Mon Sep 17 00:00:00 2001 From: Azuka Date: Tue, 30 Mar 2021 14:54:51 -0700 Subject: [PATCH] Support numeric public key ids for actions closes #1835 --- github/actions_secrets.go | 31 +++++++++ github/actions_secrets_test.go | 112 +++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/github/actions_secrets.go b/github/actions_secrets.go index 1dc16e948f8..c7e4d7b47c4 100644 --- a/github/actions_secrets.go +++ b/github/actions_secrets.go @@ -7,7 +7,9 @@ package github import ( "context" + "encoding/json" "fmt" + "strconv" ) // PublicKey represents the public key that should be used to encrypt secrets. @@ -16,6 +18,35 @@ type PublicKey struct { Key *string `json:"key"` } +// UnmarshalJSON implements the json.Unmarshaler interface. +// This ensures GitHub Enterprise versions which return a numeric key id +// do not error out when unmarshaling. +func (p *PublicKey) UnmarshalJSON(data []byte) error { + var pk struct { + KeyID interface{} `json:"key_id,string"` + Key *string `json:"key"` + } + + if err := json.Unmarshal(data, &pk); err != nil { + return err + } + + p.Key = pk.Key + + switch v := pk.KeyID.(type) { + case nil: + return nil + case string: + p.KeyID = &v + case float64: + p.KeyID = String(strconv.FormatFloat(v, 'f', -1, 64)) + default: + return fmt.Errorf("unable to unmarshal %T as a string", v) + } + + return nil +} + // GetRepoPublicKey gets a public key that should be used for secret encryption. // // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-repository-public-key diff --git a/github/actions_secrets_test.go b/github/actions_secrets_test.go index 212bcdfac76..6c06dfe20aa 100644 --- a/github/actions_secrets_test.go +++ b/github/actions_secrets_test.go @@ -7,6 +7,7 @@ package github import ( "context" + "encoding/json" "fmt" "net/http" "reflect" @@ -14,6 +15,82 @@ import ( "time" ) +func TestPublicKey_UnmarshalJSON(t *testing.T) { + var testCases = map[string]struct { + data []byte + wantPublicKey PublicKey + wantErr bool + }{ + "Empty": { + data: []byte("{}"), + wantPublicKey: PublicKey{}, + wantErr: false, + }, + "Invalid JSON": { + data: []byte("{"), + wantPublicKey: PublicKey{}, + wantErr: true, + }, + "Numeric KeyID": { + data: []byte(`{"key_id":1234,"key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`), + wantPublicKey: PublicKey{KeyID: String("1234"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")}, + wantErr: false, + }, + "String KeyID": { + data: []byte(`{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`), + wantPublicKey: PublicKey{KeyID: String("1234"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")}, + wantErr: false, + }, + "Invalid KeyID": { + data: []byte(`{"key_id":["1234"],"key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`), + wantPublicKey: PublicKey{KeyID: nil, Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")}, + wantErr: true, + }, + "Invalid Key": { + data: []byte(`{"key":123}`), + wantPublicKey: PublicKey{KeyID: nil, Key: nil}, + wantErr: true, + }, + "Nil": { + data: nil, + wantPublicKey: PublicKey{KeyID: nil, Key: nil}, + wantErr: true, + }, + "Empty String": { + data: []byte(""), + wantPublicKey: PublicKey{KeyID: nil, Key: nil}, + wantErr: true, + }, + "Missing Key": { + data: []byte(`{"key_id":"1234"}`), + wantPublicKey: PublicKey{KeyID: String("1234")}, + wantErr: false, + }, + "Missing KeyID": { + data: []byte(`{"key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`), + wantPublicKey: PublicKey{Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")}, + wantErr: false, + }, + } + + for name, tt := range testCases { + tt := tt + t.Run(name, func(t *testing.T) { + pk := PublicKey{} + err := json.Unmarshal(tt.data, &pk) + if err == nil && tt.wantErr { + t.Errorf("PublicKey.UnmarshalJSON returned nil instead of an error") + } + if err != nil && !tt.wantErr { + t.Errorf("PublicKey.UnmarshalJSON returned an unexpected error: %+v", err) + } + if !reflect.DeepEqual(tt.wantPublicKey, pk) { + t.Errorf("PublicKey.UnmarshalJSON expected public key %+v, got %+v", tt.wantPublicKey, pk) + } + }) + } +} + func TestActionsService_GetRepoPublicKey(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -49,6 +126,41 @@ func TestActionsService_GetRepoPublicKey(t *testing.T) { }) } +func TestActionsService_GetRepoPublicKeyNumeric(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/actions/secrets/public-key", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"key_id":1234,"key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) + }) + + ctx := context.Background() + key, _, err := client.Actions.GetRepoPublicKey(ctx, "o", "r") + if err != nil { + t.Errorf("Actions.GetRepoPublicKey returned error: %v", err) + } + + want := &PublicKey{KeyID: String("1234"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")} + if !reflect.DeepEqual(key, want) { + t.Errorf("Actions.GetRepoPublicKey returned %+v, want %+v", key, want) + } + + const methodName = "GetRepoPublicKey" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Actions.GetRepoPublicKey(ctx, "\n", "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Actions.GetRepoPublicKey(ctx, "o", "r") + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestActionsService_ListRepoSecrets(t *testing.T) { client, mux, _, teardown := setup() defer teardown()