From 5288c3c60ec8c2a7802223f084a4ce950b5ba16a Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 27 Jul 2018 08:56:37 +1000 Subject: [PATCH] Manage account members endpoints (#196) * Add `ListAccountMembers` endpoint Creates the functionality to be able to query all members of an account. See https://api.cloudflare.com/#accounts-list-accounts * Creates a `AddAccountMember` functionality Adds the ability to create a new account member using the API. See https://api.cloudflare.com/#account-members-add-member * Add `UpdateAccountMember` to library Allows you to update existing users using the library. See https://api.cloudflare.com/#account-members-update-member * Add `DeleteAccountMember` If you can create 'em, you gotta be able to delete 'em. See https://api.cloudflare.com/#account-members-remove-member * Add `AccountMember` for querying individual members See https://api.cloudflare.com/#account-members-member-details --- account_members.go | 166 ++++++++++++++++++++++ account_members_test.go | 308 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 474 insertions(+) create mode 100644 account_members.go create mode 100644 account_members_test.go diff --git a/account_members.go b/account_members.go new file mode 100644 index 00000000000..f8f8c653161 --- /dev/null +++ b/account_members.go @@ -0,0 +1,166 @@ +package cloudflare + +import ( + "encoding/json" + "fmt" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// AccountMember is the definition of a member of an account. +type AccountMember struct { + ID string `json:"id"` + Code string `json:"code"` + User AccountMemberUserDetails `json:"user"` + Status string `json:"status"` + Roles []AccountRole `json:"roles"` +} + +// AccountMemberUserDetails outlines all the personal information about +// a member. +type AccountMemberUserDetails struct { + ID string `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + TwoFactorAuthenticationEnabled bool +} + +// AccountMembersListResponse represents the response from the list +// account members endpoint. +type AccountMembersListResponse struct { + Result []AccountMember `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccountMemberDetailResponse is the API response, containing a single +// account member. +type AccountMemberDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccountMember `json:"result"` +} + +// AccountMemberInvitation represents the invitation for a new member to +// the account. +type AccountMemberInvitation struct { + Email string `json:"email"` + Roles []string `json:"roles"` +} + +// AccountMembers returns all members of an account. +// +// API reference: https://api.cloudflare.com/#accounts-list-accounts +func (api *API) AccountMembers(accountID string, pageOpts PaginationOptions) ([]AccountMember, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := "/accounts/" + accountID + "/members" + if len(v) > 0 { + uri = uri + "?" + v.Encode() + } + + res, err := api.makeRequest("GET", uri, nil) + if err != nil { + return []AccountMember{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError) + } + + var accountMemberListresponse AccountMembersListResponse + err = json.Unmarshal(res, &accountMemberListresponse) + if err != nil { + return []AccountMember{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberListresponse.Result, accountMemberListresponse.ResultInfo, nil +} + +// CreateAccountMember invites a new member to join an account. +// +// API reference: https://api.cloudflare.com/#account-members-add-member +func (api *API) CreateAccountMember(accountID string, emailAddress string, roles []string) (AccountMember, error) { + uri := "/accounts/" + accountID + "/members" + + var newMember = AccountMemberInvitation{ + Email: emailAddress, + Roles: roles, + } + res, err := api.makeRequest("POST", uri, newMember) + if err != nil { + return AccountMember{}, errors.Wrap(err, errMakeRequestError) + } + + var accountMemberListResponse AccountMemberDetailResponse + err = json.Unmarshal(res, &accountMemberListResponse) + if err != nil { + return AccountMember{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberListResponse.Result, nil +} + +// DeleteAccountMember removes a member from an account. +// +// API reference: https://api.cloudflare.com/#account-members-remove-member +func (api *API) DeleteAccountMember(accountID string, userID string) error { + uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID) + + _, err := api.makeRequest("DELETE", uri, nil) + if err != nil { + return errors.Wrap(err, errMakeRequestError) + } + + return nil +} + +// UpdateAccountMember modifies an existing account member. +// +// API reference: https://api.cloudflare.com/#account-members-update-member +func (api *API) UpdateAccountMember(accountID string, userID string, member AccountMember) (AccountMember, error) { + uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID) + + res, err := api.makeRequest("PUT", uri, member) + if err != nil { + return AccountMember{}, errors.Wrap(err, errMakeRequestError) + } + + var accountMemberListResponse AccountMemberDetailResponse + err = json.Unmarshal(res, &accountMemberListResponse) + if err != nil { + return AccountMember{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberListResponse.Result, nil +} + +// AccountMember returns details of a single account member. +// +// API reference: https://api.cloudflare.com/#account-members-member-details +func (api *API) AccountMember(accountID string, memberID string) (AccountMember, error) { + uri := fmt.Sprintf( + "/accounts/%s/members/%s", + accountID, + memberID, + ) + + res, err := api.makeRequest("GET", uri, nil) + if err != nil { + return AccountMember{}, errors.Wrap(err, errMakeRequestError) + } + + var accountMemberResponse AccountMemberDetailResponse + err = json.Unmarshal(res, &accountMemberResponse) + if err != nil { + return AccountMember{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberResponse.Result, nil +} diff --git a/account_members_test.go b/account_members_test.go new file mode 100644 index 00000000000..6ef5168dc29 --- /dev/null +++ b/account_members_test.go @@ -0,0 +1,308 @@ +package cloudflare + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +var expectedAccountMemberStruct = AccountMember{ + ID: "4536bcfad5faccb111b47003c79917fa", + Code: "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + User: AccountMemberUserDetails{ + ID: "7c5dae5552338874e5053f2534d2767a", + FirstName: "John", + LastName: "Appleseed", + Email: "user@example.com", + TwoFactorAuthenticationEnabled: false, + }, + Status: "accepted", + Roles: []AccountRole{ + { + ID: "3536bcfad5faccb999b47003c79917fb", + Name: "Account Administrator", + Description: "Administrative access to the entire Account", + Permissions: map[string]AccountRolePermission{ + "analytics": {Read: true, Edit: true}, + "billing": {Read: true, Edit: false}, + }, + }, + }, +} + +var expectedNewAccountMemberStruct = AccountMember{ + ID: "4536bcfad5faccb111b47003c79917fa", + Code: "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + User: AccountMemberUserDetails{ + Email: "user@example.com", + TwoFactorAuthenticationEnabled: false, + }, + Status: "pending", + Roles: []AccountRole{ + { + ID: "3536bcfad5faccb999b47003c79917fb", + Name: "Account Administrator", + Description: "Administrative access to the entire Account", + Permissions: map[string]AccountRolePermission{ + "analytics": {Read: true, Edit: true}, + "billing": {Read: true, Edit: true}, + }, + }, + }, +} + +var newUpdatedAccountMemberStruct = AccountMember{ + ID: "4536bcfad5faccb111b47003c79917fa", + Code: "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + User: AccountMemberUserDetails{ + ID: "7c5dae5552338874e5053f2534d2767a", + FirstName: "John", + LastName: "Appleseeds", + Email: "new-user@example.com", + TwoFactorAuthenticationEnabled: false, + }, + Status: "accepted", + Roles: []AccountRole{ + { + ID: "3536bcfad5faccb999b47003c79917fb", + Name: "Account Administrator", + Description: "Administrative access to the entire Account", + Permissions: map[string]AccountRolePermission{ + "analytics": {Read: true, Edit: true}, + "billing": {Read: true, Edit: true}, + }, + }, + }, +} + +func TestAccountMembers(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": [ + { + "id": "4536bcfad5faccb111b47003c79917fa", + "code": "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + "user": { + "id": "7c5dae5552338874e5053f2534d2767a", + "first_name": "John", + "last_name": "Appleseed", + "email": "user@example.com", + "two_factor_authentication_enabled": false + }, + "status": "accepted", + "roles": [ + { + "id": "3536bcfad5faccb999b47003c79917fb", + "name": "Account Administrator", + "description": "Administrative access to the entire Account", + "permissions": { + "analytics": { + "read": true, + "edit": true + }, + "billing": { + "read": true, + "edit": false + } + } + } + ] + } + ], + "result_info": { + "page": 1, + "per_page": 20, + "count": 1, + "total_count": 2000 + } + } + `) + } + + mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members", handler) + want := []AccountMember{expectedAccountMemberStruct} + + actual, _, err := client.AccountMembers("01a7362d577a6c3019a474fd6f485823", PaginationOptions{}) + + if assert.NoError(t, err) { + assert.Equal(t, want, actual) + } +} + +func TestCreateAccountMember(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST", "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "id": "4536bcfad5faccb111b47003c79917fa", + "code": "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + "user": { + "id": null, + "first_name": null, + "last_name": null, + "email": "user@example.com", + "two_factor_authentication_enabled": false + }, + "status": "pending", + "roles": [{ + "id": "3536bcfad5faccb999b47003c79917fb", + "name": "Account Administrator", + "description": "Administrative access to the entire Account", + "permissions": { + "analytics": { + "read": true, + "edit": true + }, + "billing": { + "read": true, + "edit": true + } + } + }] + } + } + `) + } + + mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members", handler) + + actual, err := client.CreateAccountMember( + "01a7362d577a6c3019a474fd6f485823", + "user@example.com", + []string{"3536bcfad5faccb999b47003c79917fb"}) + + if assert.NoError(t, err) { + assert.Equal(t, expectedNewAccountMemberStruct, actual) + } +} + +func TestUpdateAccountMember(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "PUT", "Expected method 'PUT', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "id": "4536bcfad5faccb111b47003c79917fa", + "code": "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + "user": { + "id": "7c5dae5552338874e5053f2534d2767a", + "first_name": "John", + "last_name": "Appleseeds", + "email": "new-user@example.com", + "two_factor_authentication_enabled": false + }, + "status": "accepted", + "roles": [{ + "id": "3536bcfad5faccb999b47003c79917fb", + "name": "Account Administrator", + "description": "Administrative access to the entire Account", + "permissions": { + "analytics": { + "read": true, + "edit": true + }, + "billing": { + "read": true, + "edit": true + } + } + }] + }, + "result_info": { + "page": 1, + "per_page": 20, + "count": 1, + "total_count": 2000 + } + } + `) + } + + mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members/4536bcfad5faccb111b47003c79917fa", handler) + + actual, err := client.UpdateAccountMember( + "01a7362d577a6c3019a474fd6f485823", + "4536bcfad5faccb111b47003c79917fa", + newUpdatedAccountMemberStruct, + ) + + if assert.NoError(t, err) { + assert.Equal(t, newUpdatedAccountMemberStruct, actual) + } +} + +func TestAccountMember(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "id": "4536bcfad5faccb111b47003c79917fa", + "code": "05dd05cce12bbed97c0d87cd78e89bc2fd41a6cee72f27f6fc84af2e45c0fac0", + "user": { + "id": "7c5dae5552338874e5053f2534d2767a", + "first_name": "John", + "last_name": "Appleseed", + "email": "user@example.com", + "two_factor_authentication_enabled": false + }, + "status": "accepted", + "roles": [ + { + "id": "3536bcfad5faccb999b47003c79917fb", + "name": "Account Administrator", + "description": "Administrative access to the entire Account", + "permissions": { + "analytics": { + "read": true, + "edit": true + }, + "billing": { + "read": true, + "edit": false + } + } + } + ] + } + } + `) + } + + mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members/4536bcfad5faccb111b47003c79917fa", handler) + + actual, err := client.AccountMember("01a7362d577a6c3019a474fd6f485823", "4536bcfad5faccb111b47003c79917fa") + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccountMemberStruct, actual) + } +}