Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ACCT-4459: Support for domain scoped roles #1095

Merged
merged 26 commits into from Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1972534
ACCT-4459: Add initial implementation of Policy structs
Sep 21, 2022
3ee7457
ACCT-4459: Add support to create members with policies
Sep 23, 2022
1028adb
ACCT-4459: Add Permission Group APIs and move permission/resource gro…
Sep 23, 2022
61f081f
ACCT-4459: Add resource group utility methods
Sep 23, 2022
caec0ba
ACCT-4459: Added create account member with policies utility method
Sep 23, 2022
8a83a0f
ACCT-4593: Add WIP script to call relevant endpoints for zola/nonzola…
Sep 28, 2022
3c87263
ACCT-4593: Refactor to reduce clutter, list out more endpoints to test
Sep 29, 2022
402e980
ACCT-4459: Add account_members_test
Sep 29, 2022
4e222dc
ACCT-4459: Add validation to Update and test
Sep 29, 2022
cf6756d
ACCT-4459: Rename methods for backwards-compat
Sep 29, 2022
53c41b8
ACCT-4459: Add resource group documentation and testing
Sep 29, 2022
026ece7
ACCT-4459: Add permission group testing and update documentation
Sep 29, 2022
09ca723
ACCT-4593: Keep policies and roles methods, clean up others
Sep 29, 2022
b29d54e
ACCT-4593: Remove internal testing script
Sep 29, 2022
aad7838
ACCT-4459: Resolve lint errors and file structure
Sep 30, 2022
5e812bd
ACCT-4459: gofmt
Sep 30, 2022
bd5a17f
ACCT-4459: end comments in periods
Sep 30, 2022
c4f8f2e
ACCT-4459: Move CreateAccountMember methods to new experimental format
Sep 30, 2022
3a517bb
ACCT-4459: Remove utility methods
Oct 12, 2022
78850c0
simplify method usage and params
jacobbednarz Oct 12, 2022
44f1170
permission_group: simplify `Get`/`List` methods and reuse
jacobbednarz Oct 12, 2022
0c76040
test cleanup
jacobbednarz Oct 12, 2022
e4be156
permission_groups: more validation
jacobbednarz Oct 12, 2022
5f45d74
update changelog to reflect method param changes
jacobbednarz Oct 12, 2022
06f2fc7
add missing `errors`
jacobbednarz Oct 12, 2022
6f4b50f
Merge branch 'master' into imobbs/ACCT-4459-domain-scoped-roles
jacobbednarz Oct 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/1095.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
account-member: add support for domain scoped roles
```
129 changes: 108 additions & 21 deletions account_members.go
Expand Up @@ -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
Expand Down Expand Up @@ -46,9 +48,22 @@ 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 {
AccountId string
EmailAddress string
Roles []string
Policies []Policy
Status string
}

// AccountMembers returns all members of an account.
Expand Down Expand Up @@ -80,19 +95,56 @@ func (api *API) AccountMembers(ctx context.Context, accountID string, pageOpts P
// Refer to the API reference for valid statuses.
//
// 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 AccountMember{}, ErrMissingAccountID
func (api *API) CreateAccountMemberWithStatus(ctx context.Context, rc *ResourceContainer, accountID string, emailAddress string, roles []string, status string) (AccountMember, error) {
return api.CreateAccountMember(ctx, rc, CreateAccountMemberParams{
AccountId: accountID,
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. We recommend upgrading to CreateAccountMemberWithPolicies to use policies.
//
// 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 params.AccountId == "" {
if rc.Identifier == "" {
return AccountMember{}, ErrMissingAccountID
} else {
params.AccountId = rc.Identifier
}
}

invite := AccountMemberInvitation{
Email: params.EmailAddress,
Status: params.Status,
}

uri := fmt.Sprintf("/accounts/%s/members", accountID)
roles := []AccountRole{}
for i := 0; i < len(params.Roles); i++ {
roles = append(roles, AccountRole{ID: params.Roles[i]})
}
err := validateRolesAndPolicies(roles, params.Policies)
if err != nil {
return AccountMember{}, err
}

newMember := AccountMemberInvitation{
Email: emailAddress,
Roles: roles,
Status: status,
if params.Roles != nil {
invite.Roles = params.Roles
} else if params.Policies != nil {
invite.Policies = params.Policies
}
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, newMember)

uri := fmt.Sprintf("/accounts/%s/members", rc.Identifier)
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, invite)
if err != nil {
return AccountMember{}, err
}
Expand All @@ -106,12 +158,28 @@ 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.
// CreateAccountMemberWithRoles is a terse wrapper around the CreateAccountMember method
// for clarity on what permissions you're granting an AccountMember.
//
// 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, "")
func (api *API) CreateAccountMemberWithRoles(ctx context.Context, rc *ResourceContainer, accountID string, emailAddress string, roles []string) (AccountMember, error) {
return api.CreateAccountMember(ctx, rc, CreateAccountMemberParams{
AccountId: accountID,
EmailAddress: emailAddress,
Roles: roles,
})
}

// CreateAccountMemberWithPolicies invites a new member to join your account with policies.
// Policies are the replacement to legacy "roles", which enables the newest feature Domain Scoped Roles.
//
// API documentation will be coming shortly. Blog post: https://blog.cloudflare.com/domain-scoped-roles-ga/
func (api *API) CreateAccountMemberWithPolicies(ctx context.Context, rc *ResourceContainer, accountID string, emailAddress string, policies []Policy) (AccountMember, error) {
return api.CreateAccountMember(ctx, rc, CreateAccountMemberParams{
AccountId: accountID,
EmailAddress: emailAddress,
Policies: policies,
})
}

// DeleteAccountMember removes a member from an account.
Expand Down Expand Up @@ -140,6 +208,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)
Expand Down Expand Up @@ -183,3 +256,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
}