From e2db782c7714391a518243930ce3adef241bc3f5 Mon Sep 17 00:00:00 2001 From: Jesse Li Date: Thu, 27 Oct 2022 10:06:13 -0400 Subject: [PATCH] Support Access service token rotation --- .changelog/1120.txt | 3 +++ access_service_tokens.go | 41 +++++++++++++++++++++++++++++++++ access_service_tokens_test.go | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 .changelog/1120.txt diff --git a/.changelog/1120.txt b/.changelog/1120.txt new file mode 100644 index 00000000000..ca0c26758c9 --- /dev/null +++ b/.changelog/1120.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +access: add support for service token rotation +``` diff --git a/access_service_tokens.go b/access_service_tokens.go index da3debf2278..d0f5314f20b 100644 --- a/access_service_tokens.go +++ b/access_service_tokens.go @@ -54,6 +54,18 @@ type AccessServiceTokenCreateResponse struct { ClientSecret string `json:"client_secret"` } +// AccessServiceTokenRotateResponse is the same API response as the Create +// operation. +type AccessServiceTokenRotateResponse struct { + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + ExpiresAt *time.Time `json:"expires_at"` + ID string `json:"id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + // AccessServiceTokensListResponse represents the response from the list // Access Service Tokens endpoint. type AccessServiceTokensListResponse struct { @@ -98,6 +110,15 @@ type AccessServiceTokensRefreshDetailResponse struct { Result AccessServiceTokenRefreshResponse `json:"result"` } +// AccessServiceTokensRotateSecretDetailResponse is the API response, containing a +// single Access Service Token. +type AccessServiceTokensRotateSecretDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessServiceTokenRotateResponse `json:"result"` +} + // AccessServiceTokens returns all Access Service Tokens for an account. // // API reference: https://api.cloudflare.com/#access-service-tokens-list-access-service-tokens @@ -254,3 +275,23 @@ func (api *API) RefreshAccessServiceToken(ctx context.Context, rc *ResourceConta return accessServiceTokenRefresh.Result, nil } + +// RotateAccessServiceToken rotates the client secret of an Access Service +// Token in place. +// API reference: https://api.cloudflare.com/#access-service-tokens-rotate-a-service-token +func (api *API) RotateAccessServiceToken(ctx context.Context, rc *ResourceContainer, id string) (AccessServiceTokenRotateResponse, error) { + uri := fmt.Sprintf("/%s/%s/access/service_tokens/%s/rotate", rc.Level, rc.Identifier, id) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return AccessServiceTokenRotateResponse{}, err + } + + var accessServiceTokenRotate AccessServiceTokensRotateSecretDetailResponse + err = json.Unmarshal(res, &accessServiceTokenRotate) + if err != nil { + return AccessServiceTokenRotateResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return accessServiceTokenRotate.Result, nil +} diff --git a/access_service_tokens_test.go b/access_service_tokens_test.go index 391a88b92c3..cbaa69c4652 100644 --- a/access_service_tokens_test.go +++ b/access_service_tokens_test.go @@ -208,6 +208,49 @@ func TestRefreshAccessServiceToken(t *testing.T) { } } +func TestRotateAccessServiceToken(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "expires_at": "2015-01-01T05:20:00.12345Z", + "id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415", + "name": "CI/CD token", + "client_id": "88bf3b6d86161464f6509f7219099e57.access.example.com", + "client_secret": "bdd31cbc4dec990953e39163fbbb194c93313ca9f0a6e420346af9d326b1d2a5" + } + } + `) + } + + expected := AccessServiceTokenRotateResponse{ + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + ExpiresAt: &expiresAt, + ID: "f174e90a-fafe-4643-bbbc-4a0ed4fc8415", + Name: "CI/CD token", + ClientID: "88bf3b6d86161464f6509f7219099e57.access.example.com", + ClientSecret: "bdd31cbc4dec990953e39163fbbb194c93313ca9f0a6e420346af9d326b1d2a5", + } + + mux.HandleFunc("/accounts/"+testAccountID+"/access/service_tokens/f174e90a-fafe-4643-bbbc-4a0ed4fc8415/rotate", handler) + + actual, err := client.RotateAccessServiceToken(context.Background(), AccountIdentifier(testAccountID), "f174e90a-fafe-4643-bbbc-4a0ed4fc8415") + + if assert.NoError(t, err) { + assert.Equal(t, expected, actual) + } +} + func TestDeleteAccessServiceToken(t *testing.T) { setup() defer teardown()