diff --git a/.changelog/1095.txt b/.changelog/1095.txt new file mode 100644 index 000000000..aeab97d4a --- /dev/null +++ b/.changelog/1095.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +account_member: add support for domain scoped roles +``` + +```release-note:breaking-change +account_member: `CreateAccountMember` has been updated to accept a `CreateAccountMemberParams` struct instead of multiple parameters +``` diff --git a/account_members.go b/account_members.go index 052bcf54a..d197fda1d 100644 --- a/account_members.go +++ b/account_members.go @@ -3,17 +3,19 @@ package cloudflare import ( "context" "encoding/json" + "errors" "fmt" "net/http" ) // 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"` + ID string `json:"id"` + Code string `json:"code"` + User AccountMemberUserDetails `json:"user"` + Status string `json:"status"` + Roles []AccountRole `json:"roles,omitempty"` + Policies []Policy `json:"policies,omitempty"` } // AccountMemberUserDetails outlines all the personal information about @@ -46,9 +48,21 @@ type AccountMemberDetailResponse struct { // AccountMemberInvitation represents the invitation for a new member to // the account. type AccountMemberInvitation struct { - Email string `json:"email"` - Roles []string `json:"roles"` - Status string `json:"status,omitempty"` + Email string `json:"email"` + Roles []string `json:"roles,omitempty"` + Policies []Policy `json:"policies,omitempty"` + Status string `json:"status,omitempty"` +} + +const errMissingMemberRolesOrPolicies = "account member must be created with roles or policies (not both)" + +var ErrMissingMemberRolesOrPolicies = errors.New(errMissingMemberRolesOrPolicies) + +type CreateAccountMemberParams struct { + EmailAddress string + Roles []string + Policies []Policy + Status string } // AccountMembers returns all members of an account. @@ -79,20 +93,54 @@ func (api *API) AccountMembers(ctx context.Context, accountID string, pageOpts P // // Refer to the API reference for valid statuses. // +// Deprecated: Use `CreateAccountMember` with a `Status` field instead. +// // API reference: https://api.cloudflare.com/#account-members-add-member func (api *API) CreateAccountMemberWithStatus(ctx context.Context, accountID string, emailAddress string, roles []string, status string) (AccountMember, error) { - if accountID == "" { + return api.CreateAccountMember(ctx, AccountIdentifier(accountID), CreateAccountMemberParams{ + EmailAddress: emailAddress, + Roles: roles, + Status: status, + }) +} + +// CreateAccountMember invites a new member to join an account with roles. +// The member will be placed into "pending" status and receive an email confirmation. +// NOTE: If you are currently enrolled in Domain Scoped Roles, your roles will +// be converted to policies upon member invitation. +// +// API reference: https://api.cloudflare.com/#account-members-add-member +func (api *API) CreateAccountMember(ctx context.Context, rc *ResourceContainer, params CreateAccountMemberParams) (AccountMember, error) { + if rc.Level != AccountRouteLevel { + return AccountMember{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level) + } + + if rc.Identifier == "" { return AccountMember{}, ErrMissingAccountID } - uri := fmt.Sprintf("/accounts/%s/members", accountID) + invite := AccountMemberInvitation{ + Email: params.EmailAddress, + Status: params.Status, + } - newMember := AccountMemberInvitation{ - Email: emailAddress, - Roles: roles, - Status: status, + roles := []AccountRole{} + for i := 0; i < len(params.Roles); i++ { + roles = append(roles, AccountRole{ID: params.Roles[i]}) } - res, err := api.makeRequestContext(ctx, http.MethodPost, uri, newMember) + err := validateRolesAndPolicies(roles, params.Policies) + if err != nil { + return AccountMember{}, err + } + + if params.Roles != nil { + invite.Roles = params.Roles + } else if params.Policies != nil { + invite.Policies = params.Policies + } + + uri := fmt.Sprintf("/accounts/%s/members", rc.Identifier) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, invite) if err != nil { return AccountMember{}, err } @@ -106,14 +154,6 @@ func (api *API) CreateAccountMemberWithStatus(ctx context.Context, accountID str return accountMemberListResponse.Result, nil } -// CreateAccountMember invites a new member to join an account. -// The member will be placed into "pending" status and receive an email confirmation. -// -// API reference: https://api.cloudflare.com/#account-members-add-member -func (api *API) CreateAccountMember(ctx context.Context, accountID string, emailAddress string, roles []string) (AccountMember, error) { - return api.CreateAccountMemberWithStatus(ctx, accountID, emailAddress, roles, "") -} - // DeleteAccountMember removes a member from an account. // // API reference: https://api.cloudflare.com/#account-members-remove-member @@ -140,6 +180,11 @@ func (api *API) UpdateAccountMember(ctx context.Context, accountID string, userI return AccountMember{}, ErrMissingAccountID } + err := validateRolesAndPolicies(member.Roles, member.Policies) + if err != nil { + return AccountMember{}, err + } + uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID) res, err := api.makeRequestContext(ctx, http.MethodPut, uri, member) @@ -183,3 +228,17 @@ func (api *API) AccountMember(ctx context.Context, accountID string, memberID st return accountMemberResponse.Result, nil } + +// validateRolesAndPolicies ensures either roles or policies are provided in +// CreateAccountMember requests, but not both. +func validateRolesAndPolicies(roles []AccountRole, policies []Policy) error { + hasRoles := len(roles) > 0 + hasPolicies := len(policies) > 0 + hasRolesOrPolicies := hasRoles || hasPolicies + hasRolesAndPolicies := hasRoles && hasPolicies + hasCorrectPermissions := hasRolesOrPolicies && !hasRolesAndPolicies + if !hasCorrectPermissions { + return ErrMissingMemberRolesOrPolicies + } + return nil +} diff --git a/account_members_test.go b/account_members_test.go index 418b62284..b4d8d8348 100644 --- a/account_members_test.go +++ b/account_members_test.go @@ -99,6 +99,43 @@ var newUpdatedAccountMemberStruct = AccountMember{ }, } +var mockPolicy = Policy{ + ID: "mock-policy-id", + PermissionGroups: []PermissionGroup{{ + ID: "mock-permission-group-id", + Name: "mock-permission-group-name", + Permissions: []Permission{{ + ID: "mock-permission-id", + Key: "mock-permission-key", + }}, + }}, + ResourceGroups: []ResourceGroup{{ + ID: "mock-resource-group-id", + Name: "mock-resource-group-name", + Scope: Scope{ + Key: "mock-resource-group-name", + ScopeObjects: []ScopeObject{{ + Key: "*", + }}, + }, + }}, + Access: "allow", +} + +var expectedNewAccountMemberWithPoliciesStruct = AccountMember{ + ID: "new-member-with-polcies-id", + Code: "new-member-with-policies-code", + User: AccountMemberUserDetails{ + ID: "new-member-with-policies-user-id", + FirstName: "John", + LastName: "Appleseed", + Email: "user@example.com", + TwoFactorAuthenticationEnabled: false, + }, + Status: "accepted", + Policies: []Policy{mockPolicy}, +} + func TestAccountMembers(t *testing.T) { setup() defer teardown() @@ -151,7 +188,7 @@ func TestAccountMembers(t *testing.T) { `) } - mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/members", handler) want := []AccountMember{expectedAccountMemberStruct} actual, _, err := client.AccountMembers(context.Background(), "01a7362d577a6c3019a474fd6f485823", PaginationOptions{}) @@ -214,7 +251,7 @@ func TestCreateAccountMemberWithStatus(t *testing.T) { `) } - mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/members", handler) actual, err := client.CreateAccountMemberWithStatus(context.Background(), "01a7362d577a6c3019a474fd6f485823", "user@example.com", []string{"3536bcfad5faccb999b47003c79917fb"}, "accepted") @@ -265,20 +302,115 @@ func TestCreateAccountMember(t *testing.T) { `) } - mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/members", handler) + + createAccountParams := CreateAccountMemberParams{ + EmailAddress: "user@example.com", + Roles: []string{"3536bcfad5faccb999b47003c79917fb"}, + } - actual, err := client.CreateAccountMember(context.Background(), "01a7362d577a6c3019a474fd6f485823", "user@example.com", []string{"3536bcfad5faccb999b47003c79917fb"}) + actual, err := client.CreateAccountMember(context.Background(), AccountIdentifier("01a7362d577a6c3019a474fd6f485823"), createAccountParams) if assert.NoError(t, err) { assert.Equal(t, expectedNewAccountMemberStruct, actual) } } +func TestCreateAccountMemberWithPolicies(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": { + "id": "new-member-with-polcies-id", + "code": "new-member-with-policies-code", + "user": { + "id": "new-member-with-policies-user-id", + "first_name": "John", + "last_name": "Appleseed", + "email": "user@example.com", + "two_factor_authentication_enabled": false + }, + "status": "accepted", + "policies": [{ + "id": "mock-policy-id", + "permission_groups": [{ + "id": "mock-permission-group-id", + "name": "mock-permission-group-name", + "permissions": [{ + "id": "mock-permission-id", + "key": "mock-permission-key" + }] + }], + "resource_groups": [{ + "id": "mock-resource-group-id", + "name": "mock-resource-group-name", + "scope": { + "key": "mock-resource-group-name", + "objects": [{ + "key": "*" + }] + } + }], + "access": "allow" + }] + } + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/members", handler) + actual, err := client.CreateAccountMember(context.Background(), AccountIdentifier("01a7362d577a6c3019a474fd6f485823"), CreateAccountMemberParams{ + EmailAddress: "user@example.com", + Roles: nil, + Policies: []Policy{mockPolicy}, + Status: "", + }) + + if assert.NoError(t, err) { + assert.Equal(t, expectedNewAccountMemberWithPoliciesStruct, actual) + } +} + +func TestCreateAccountMemberWithRolesAndPoliciesErr(t *testing.T) { + setup() + defer teardown() + + accountResource := &ResourceContainer{ + Level: AccountRouteLevel, + Identifier: "01a7362d577a6c3019a474fd6f485823", + } + _, err := client.CreateAccountMember(context.Background(), accountResource, CreateAccountMemberParams{ + EmailAddress: "user@example.com", + Roles: []string{"fake-role-id"}, + Policies: []Policy{mockPolicy}, + Status: "active", + }) + + if assert.Error(t, err) { + assert.Equal(t, err, ErrMissingMemberRolesOrPolicies) + } +} + func TestCreateAccountMemberWithoutAccountID(t *testing.T) { setup() defer teardown() - _, err := client.CreateAccountMember(context.Background(), "", "user@example.com", []string{"3536bcfad5faccb999b47003c79917fb"}) + accountResource := &ResourceContainer{ + Level: AccountRouteLevel, + Identifier: "", + } + _, err := client.CreateAccountMember(context.Background(), accountResource, CreateAccountMemberParams{ + EmailAddress: "user@example.com", + Roles: []string{"fake-role-id"}, + Status: "active", + }) if assert.Error(t, err) { assert.Equal(t, err.Error(), errMissingAccountID) @@ -333,7 +465,7 @@ func TestUpdateAccountMember(t *testing.T) { `) } - mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members/4536bcfad5faccb111b47003c79917fa", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/members/4536bcfad5faccb111b47003c79917fa", handler) actual, err := client.UpdateAccountMember(context.Background(), "01a7362d577a6c3019a474fd6f485823", "4536bcfad5faccb111b47003c79917fa", newUpdatedAccountMemberStruct) @@ -342,6 +474,89 @@ func TestUpdateAccountMember(t *testing.T) { } } +func TestUpdateAccountMemberWithPolicies(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "id": "new-member-with-polcies-id", + "code": "new-member-with-policies-code", + "user": { + "id": "new-member-with-policies-user-id", + "first_name": "John", + "last_name": "Appleseed", + "email": "user@example.com", + "two_factor_authentication_enabled": false + }, + "status": "accepted", + "policies": [{ + "id": "mock-policy-id", + "permission_groups": [{ + "id": "mock-permission-group-id", + "name": "mock-permission-group-name", + "permissions": [{ + "id": "mock-permission-id", + "key": "mock-permission-key" + }] + }], + "resource_groups": [{ + "id": "mock-resource-group-id", + "name": "mock-resource-group-name", + "scope": { + "key": "mock-resource-group-name", + "objects": [{ + "key": "*" + }] + } + }], + "access": "allow" + }] + }, + "result_info": { + "page": 1, + "per_page": 20, + "count": 1, + "total_count": 2000 + } + }`) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/members/new-member-with-polcies-id", handler) + + actual, err := client.UpdateAccountMember(context.Background(), "01a7362d577a6c3019a474fd6f485823", "new-member-with-polcies-id", expectedNewAccountMemberWithPoliciesStruct) + + if assert.NoError(t, err) { + assert.Equal(t, expectedNewAccountMemberWithPoliciesStruct, actual) + } +} + +func UpdateAccountMemberWithRolesAndPoliciesErr(t *testing.T) { + setup() + defer teardown() + + _, err := client.UpdateAccountMember(context.Background(), + "01a7362d577a6c3019a474fd6f485823", + "", + AccountMember{ + Roles: []AccountRole{{ + ID: "some-role", + }}, + Policies: []Policy{mockPolicy}, + }, + ) + + if assert.Error(t, err) { + assert.Equal(t, err, ErrMissingMemberRolesOrPolicies) + } +} + func TestUpdateAccountMemberWithoutAccountID(t *testing.T) { setup() defer teardown() @@ -397,7 +612,7 @@ func TestAccountMember(t *testing.T) { `) } - mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/members/4536bcfad5faccb111b47003c79917fa", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/members/4536bcfad5faccb111b47003c79917fa", handler) actual, err := client.AccountMember(context.Background(), "01a7362d577a6c3019a474fd6f485823", "4536bcfad5faccb111b47003c79917fa") diff --git a/permission_group.go b/permission_group.go new file mode 100644 index 000000000..c6468d006 --- /dev/null +++ b/permission_group.go @@ -0,0 +1,98 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" +) + +type PermissionGroup struct { + ID string `json:"id"` + Name string `json:"name"` + Meta map[string]string `json:"meta"` + Permissions []Permission `json:"permissions"` +} + +type Permission struct { + ID string `json:"id"` + Key string `json:"key"` + Attributes map[string]string `json:"attributes,omitempty"` // same as Meta in other structs +} + +type PermissionGroupListResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []PermissionGroup `json:"result"` +} + +type PermissionGroupDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result PermissionGroup `json:"result"` +} + +type ListPermissionGroupParams struct { + Depth int `url:"depth,omitempty"` + RoleName string `url:"name,omitempty"` +} + +const errMissingPermissionGroupID = "missing required permission group ID" + +var ErrMissingPermissionGroupID = errors.New(errMissingPermissionGroupID) + +// GetPermissionGroup returns a specific permission group from the API given +// the account ID and permission group ID. +func (api *API) GetPermissionGroup(ctx context.Context, rc *ResourceContainer, permissionGroupId string) (PermissionGroup, error) { + if rc.Level != AccountRouteLevel { + return PermissionGroup{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level) + } + + if rc.Identifier == "" { + return PermissionGroup{}, ErrMissingAccountID + } + + if permissionGroupId == "" { + return PermissionGroup{}, ErrMissingPermissionGroupID + } + + uri := fmt.Sprintf("/accounts/%s/iam/permission_groups/%s?depth=2", rc.Identifier, permissionGroupId) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PermissionGroup{}, err + } + + var permissionGroupResponse PermissionGroupDetailResponse + err = json.Unmarshal(res, &permissionGroupResponse) + if err != nil { + return PermissionGroup{}, err + } + + return permissionGroupResponse.Result, nil +} + +// ListPermissionGroups returns all valid permission groups for the provided +// parameters. +func (api *API) ListPermissionGroups(ctx context.Context, rc *ResourceContainer, params ListPermissionGroupParams) ([]PermissionGroup, error) { + if rc.Level != AccountRouteLevel { + return []PermissionGroup{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level) + } + + params.Depth = 2 + uri := buildURI(fmt.Sprintf("/accounts/%s/iam/permission_groups", rc.Identifier), params) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []PermissionGroup{}, err + } + + var permissionGroupResponse PermissionGroupListResponse + err = json.Unmarshal(res, &permissionGroupResponse) + if err != nil { + return []PermissionGroup{}, err + } + + return permissionGroupResponse.Result, nil +} diff --git a/permission_group_test.go b/permission_group_test.go new file mode 100644 index 000000000..d3520db9e --- /dev/null +++ b/permission_group_test.go @@ -0,0 +1,152 @@ +package cloudflare + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +var mockPermissionGroup = PermissionGroup{ + ID: "f08020434ba14a0bb46bd9ff52f23b04", + Name: "Fake Permission Group", + Meta: map[string]string{ + "description": "Can represent a permission group", + }, + Permissions: []Permission{{ + ID: "7d42552322884d19bb63ed7f69b5ac21", + Key: "com.cloudflare.api.account.fake.permission", + Attributes: map[string]string{ + "legacy_name": "#fake:permission", + }, + }}, +} + +func TestListPermissionGroup_ByName(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "result": [ + { + "id": "f08020434ba14a0bb46bd9ff52f23b04", + "name": "Fake Permission Group", + "status": "V", + "meta": { + "description": "Can represent a permission group" + }, + "created_on": "2022-08-29T16:58:29.745574Z", + "modified_on": "2022-09-12T08:26:37.255907Z", + "permissions": [ + { + "id": "7d42552322884d19bb63ed7f69b5ac21", + "key": "com.cloudflare.api.account.fake.permission", + "attributes": { + "legacy_name": "#fake:permission" + } + } + ] + } + ], + "success": true, + "errors": [], + "messages": [] + }`) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/iam/permission_groups", handler) + + result, err := client.ListPermissionGroups(context.Background(), AccountIdentifier(testAccountID), ListPermissionGroupParams{RoleName: "fake-permission-group-name"}) + if assert.NoError(t, err) { + assert.Equal(t, result, []PermissionGroup{mockPermissionGroup}) + } +} + +func TestGetPermissionGroup(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "result": { + "id": "f08020434ba14a0bb46bd9ff52f23b04", + "name": "Fake Permission Group", + "status": "V", + "meta": { + "description": "Can represent a permission group" + }, + "created_on": "2022-08-29T16:58:29.745574Z", + "modified_on": "2022-09-12T08:26:37.255907Z", + "permissions": [ + { + "id": "7d42552322884d19bb63ed7f69b5ac21", + "key": "com.cloudflare.api.account.fake.permission", + "attributes": { + "legacy_name": "#fake:permission" + } + } + ] + }, + "success": true, + "errors": [], + "messages": [] + }`) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/iam/permission_groups/fake-permission-group-id", handler) + + result, err := client.GetPermissionGroup(context.Background(), AccountIdentifier(testAccountID), "fake-permission-group-id") + if assert.NoError(t, err) { + assert.Equal(t, result, mockPermissionGroup) + } +} + +func TestPermissionGroups(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "result": [ + { + "id": "f08020434ba14a0bb46bd9ff52f23b04", + "name": "Fake Permission Group", + "status": "V", + "meta": { + "description": "Can represent a permission group" + }, + "created_on": "2022-08-29T16:58:29.745574Z", + "modified_on": "2022-09-12T08:26:37.255907Z", + "permissions": [ + { + "id": "7d42552322884d19bb63ed7f69b5ac21", + "key": "com.cloudflare.api.account.fake.permission", + "attributes": { + "legacy_name": "#fake:permission" + } + } + ] + } + ], + "success": true, + "errors": [], + "messages": [] + }`) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/iam/permission_groups", handler) + + result, err := client.ListPermissionGroups(context.Background(), AccountIdentifier(testAccountID), ListPermissionGroupParams{}) + if assert.NoError(t, err) { + assert.Equal(t, result, []PermissionGroup{mockPermissionGroup}) + } +} diff --git a/policy.go b/policy.go new file mode 100644 index 000000000..807724120 --- /dev/null +++ b/policy.go @@ -0,0 +1,8 @@ +package cloudflare + +type Policy struct { + ID string `json:"id"` + PermissionGroups []PermissionGroup `json:"permission_groups"` + ResourceGroups []ResourceGroup `json:"resource_groups"` + Access string `json:"access"` +} diff --git a/resource_group.go b/resource_group.go new file mode 100644 index 000000000..6d25ffb5e --- /dev/null +++ b/resource_group.go @@ -0,0 +1,53 @@ +package cloudflare + +import "fmt" + +type ResourceGroup struct { + ID string `json:"id"` + Name string `json:"name"` + Meta map[string]string `json:"meta"` + Scope Scope `json:"scope"` +} + +type Scope struct { + Key string `json:"key"` + ScopeObjects []ScopeObject `json:"objects"` +} + +type ScopeObject struct { + Key string `json:"key"` +} + +// NewResourceGroupForZone takes an existing zone and provides a resource group +// to be used within a Policy that allows access to that zone. +func NewResourceGroupForZone(zone Zone) ResourceGroup { + return NewResourceGroup(fmt.Sprintf("com.cloudflare.api.account.zone.%s", zone.ID)) +} + +// NewResourceGroupForAccount takes an existing zone and provides a resource group +// to be used within a Policy that allows access to that account. +func NewResourceGroupForAccount(account Account) ResourceGroup { + return NewResourceGroup(fmt.Sprintf("com.cloudflare.api.account.%s", account.ID)) +} + +// NewResourceGroup takes a Cloudflare-formatted key (e.g. 'com.cloudflare.api.%s') and +// returns a resource group to be used within a Policy to allow access to that resource. +func NewResourceGroup(key string) ResourceGroup { + scope := Scope{ + Key: key, + ScopeObjects: []ScopeObject{ + { + Key: "*", + }, + }, + } + resourceGroup := ResourceGroup{ + ID: "", + Name: key, + Meta: map[string]string{ + "editable": "false", + }, + Scope: scope, + } + return resourceGroup +} diff --git a/resource_group_test.go b/resource_group_test.go new file mode 100644 index 000000000..ca4b6dc20 --- /dev/null +++ b/resource_group_test.go @@ -0,0 +1,46 @@ +package cloudflare + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewResourceGroup(t *testing.T) { + setup() + defer teardown() + + key := "com.cloudflare.test.1" + rg := NewResourceGroup(key) + + assert.Equal(t, rg.Name, key) + assert.Equal(t, rg.Scope.Key, key) +} + +func TestNewResourceGroupForAccount(t *testing.T) { + setup() + defer teardown() + + id := "some-fake-account-id" + rg := NewResourceGroupForAccount(Account{ + ID: id, + }) + + key := fmt.Sprintf("com.cloudflare.api.account.%s", id) + assert.Equal(t, rg.Name, key) + assert.Equal(t, rg.Scope.Key, key) +} + +func TestNewResourceGroupForZone(t *testing.T) { + setup() + defer teardown() + + id := "some-fake-zone-id" + rg := NewResourceGroupForZone(Zone{ + ID: id, + }) + + key := fmt.Sprintf("com.cloudflare.api.account.zone.%s", id) + assert.Equal(t, rg.Name, key) + assert.Equal(t, rg.Scope.Key, key) +}