Skip to content

Commit

Permalink
DXCDT-188: Assigning org members via auth0_organization_member reso…
Browse files Browse the repository at this point in the history
…urce (#256)

* Adding auth0_organization_member resource along with tests

* Adding temporary code to get tests to pass

* Adding comments to testing workarounds

* Removing placeholder configuration, adding comments to test cases

* Adding recordings

* Removing reference of member ID in favor of technically more correct user ID

* Removing stale TODO

* Adding stronger test assertions

* Setting unique ID for member

* Update auth0/resource_auth0_organization_member.go

Co-authored-by: Sergiu Ghitea <[email protected]>

* Update auth0/resource_auth0_organization_member.go

Co-authored-by: Sergiu Ghitea <[email protected]>

* Update auth0/resource_auth0_organization_member.go

Co-authored-by: Sergiu Ghitea <[email protected]>

* Update auth0/resource_auth0_organization_member.go

Co-authored-by: Sergiu Ghitea <[email protected]>

* Update auth0/resource_auth0_organization_member.go

Co-authored-by: Sergiu Ghitea <[email protected]>

* Update auth0/resource_auth0_organization_member.go

Co-authored-by: Sergiu Ghitea <[email protected]>

* Splitting functions

* Adding check for 404

* Adding example

* Adding missing comma

* Adding corrected Get functions

* Refining HTTP not found checks, removing sweepers

* Allowing any order for test results

* Chaining roles for testing predictability

Co-authored-by: Will Vedder <[email protected]>
Co-authored-by: Sergiu Ghitea <[email protected]>
  • Loading branch information
3 people authored Jul 22, 2022
1 parent e9d3fb5 commit cad77de
Show file tree
Hide file tree
Showing 6 changed files with 2,425 additions and 0 deletions.
1 change: 1 addition & 0 deletions auth0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func Provider() *schema.Provider {
"auth0_branding": newBranding(),
"auth0_guardian": newGuardian(),
"auth0_organization": newOrganization(),
"auth0_organization_member": newOrganizationMember(),
"auth0_action": newAction(),
"auth0_trigger_binding": newTriggerBinding(),
"auth0_attack_protection": newAttackProtection(),
Expand Down
172 changes: 172 additions & 0 deletions auth0/resource_auth0_organization_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package auth0

import (
"context"
"fmt"
"net/http"

"github.com/auth0/go-auth0/management"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func newOrganizationMember() *schema.Resource {
return &schema.Resource{
Description: "This resource is used to manage the assignment of members and their roles within an organization.",
CreateContext: createOrganizationMember,
ReadContext: readOrganizationMember,
UpdateContext: updateOrganizationMember,
DeleteContext: deleteOrganizationMember,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"organization_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of the organization to assign the member to.",
},
"user_id": {
Type: schema.TypeString,
Required: true,
Description: "ID of the user to add as an organization member.",
},
"roles": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "The role ID(s) to assign to the organization member.",
Optional: true,
},
},
}
}

func createOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
userID := d.Get("user_id").(string)
orgID := d.Get("organization_id").(string)

api := m.(*management.Management)
if err := api.Organization.AddMembers(orgID, []string{userID}); err != nil {
return diag.FromErr(err)
}

d.SetId(resource.UniqueId())

if err := assignRoles(d, m); err != nil {
return diag.FromErr(fmt.Errorf("failed to assign roles to organization member: %w", err))
}

return readOrganizationMember(ctx, d, m)
}

func assignRoles(d *schema.ResourceData, m interface{}) error {
orgID := d.Get("organization_id").(string)
userID := d.Get("user_id").(string)

add, rm := Diff(d, "roles")

err := addMemberRoles(orgID, userID, add.List(), m)
if err != nil {
return err
}

err = removeMemberRoles(orgID, userID, rm.List(), m)
if err != nil {
return err
}

return nil
}

func removeMemberRoles(orgID string, userID string, roles []interface{}, m interface{}) error {
api := m.(*management.Management)

rolesToRemove := []string{}
for _, r := range roles {
rolesToRemove = append(rolesToRemove, r.(string))
}
if len(rolesToRemove) == 0 {
return nil
}

err := api.Organization.DeleteMemberRoles(orgID, userID, rolesToRemove)
if err != nil {
// Ignore 404 errors as the role may have been deleted
// prior to un-assigning them from the member.
if err, ok := err.(management.Error); ok && err.Status() == http.StatusNotFound {
return nil
}
}

return nil
}

func addMemberRoles(orgID string, userID string, roles []interface{}, m interface{}) error {
api := m.(*management.Management)

rolesToAssign := []string{}
for _, r := range roles {
rolesToAssign = append(rolesToAssign, r.(string))
}
if len(rolesToAssign) == 0 {
return nil
}

err := api.Organization.AssignMemberRoles(orgID, userID, rolesToAssign)
if err != nil {
return err
}

return nil
}

func readOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api := m.(*management.Management)

orgID := d.Get("organization_id").(string)
userID := d.Get("user_id").(string)

roles, err := api.Organization.MemberRoles(orgID, userID)
if err != nil {
return diag.FromErr(err)
}

rolesToSet := []interface{}{}
for _, role := range roles.Roles {
rolesToSet = append(rolesToSet, role.ID)
}

result := multierror.Append(
d.Set("roles", rolesToSet),
)

return diag.FromErr(result.ErrorOrNil())
}

func updateOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
if err := assignRoles(d, m); err != nil {
return diag.FromErr(fmt.Errorf("failed to assign members to organization. %w", err))
}

return readOrganizationMember(ctx, d, m)
}

func deleteOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api := m.(*management.Management)

orgID := d.Get("organization_id").(string)
userID := d.Get("user_id").(string)

if err := api.Organization.DeleteMember(orgID, []string{userID}); err != nil {
if err, ok := err.(management.Error); ok && err.Status() == http.StatusNotFound {
return nil
}
return diag.FromErr(err)
}

d.SetId("")

return nil
}
122 changes: 122 additions & 0 deletions auth0/resource_auth0_organization_member_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package auth0

import (
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"

"github.com/auth0/terraform-provider-auth0/auth0/internal/template"
)

func TestAccOrganizationMember(t *testing.T) {
httpRecorder := configureHTTPRecorder(t)

testName := strings.ToLower(t.Name())

resource.Test(t, resource.TestCase{
ProviderFactories: testProviders(httpRecorder),
Steps: []resource.TestStep{{
Config: template.ParseTestName(testAccOrganizationMembersAux+`
resource auth0_organization_member test_member {
depends_on = [ auth0_user.user, auth0_organization.some_org ]
organization_id = auth0_organization.some_org.id
user_id = auth0_user.user.id
}
`, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "0"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"),
),
},
{
Config: template.ParseTestName(testAccOrganizationMembersAux+`
resource auth0_organization_member test_member {
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader ]
organization_id = auth0_organization.some_org.id
user_id = auth0_user.user.id
roles = [ auth0_role.reader.id ] // Adding role
}
`, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"),
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "1"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.reader", "id"),
),
},
{
Config: template.ParseTestName(testAccOrganizationMembersAux+`
resource auth0_organization_member test_member {
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader, auth0_role.admin ]
organization_id = auth0_organization.some_org.id
user_id = auth0_user.user.id
roles = [ auth0_role.reader.id, auth0_role.admin.id ] // Adding an additional role
}
`, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"),
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "2"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.reader", "id"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.admin", "id"),
),
},
{
Config: template.ParseTestName(testAccOrganizationMembersAux+`
resource auth0_organization_member test_member {
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader, auth0_role.admin ]
organization_id = auth0_organization.some_org.id
user_id = auth0_user.user.id
roles = [ auth0_role.admin.id ] // Removing a role
}
`, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"),
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "1"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.admin", "id"),
),
},
{
Config: template.ParseTestName(testAccOrganizationMembersAux+
`
resource auth0_organization_member test_member {
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader, auth0_role.admin ]
organization_id = auth0_organization.some_org.id
user_id = auth0_user.user.id
// Removing roles entirely
}`, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "0"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"),
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"),
),
},
},
})
}

const testAccOrganizationMembersAux = `
resource auth0_role reader {
name = "Reader - {{.testName}}"
}
resource auth0_role admin {
depends_on = [ auth0_role.reader ]
name = "Admin - {{.testName}}"
}
resource auth0_user user {
email = "{{.testName}}@auth0.com"
connection_name = "Username-Password-Authentication"
email_verified = true
password = "MyPass123$"
}
resource auth0_organization some_org{
name = "some-org-{{.testName}}"
display_name = "{{.testName}}"
}
`
Loading

0 comments on commit cad77de

Please sign in to comment.