Skip to content

Commit

Permalink
Merge pull request #1095 from cloudflare/imobbs/ACCT-4459-domain-scop…
Browse files Browse the repository at this point in the history
…ed-roles

ACCT-4459: Support for domain scoped roles
  • Loading branch information
jacobbednarz committed Oct 13, 2022
2 parents 136f942 + 6f4b50f commit 40a64a8
Show file tree
Hide file tree
Showing 8 changed files with 668 additions and 30 deletions.
7 changes: 7 additions & 0 deletions .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
```
105 changes: 82 additions & 23 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,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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
}

0 comments on commit 40a64a8

Please sign in to comment.