From 1e87b2d38eb19c5b043f1ef0900177773be67a47 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Mon, 25 Oct 2021 10:26:35 +0200 Subject: [PATCH 01/12] Update go-auth0 dependency --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 473c5caa..9c44e24b 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.16 require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/terraform-plugin-sdk v1.16.1 - gopkg.in/auth0.v5 v5.19.2 + gopkg.in/auth0.v5 v5.20.0 ) diff --git a/go.sum b/go.sum index 5e20ffd1..953de6d0 100644 --- a/go.sum +++ b/go.sum @@ -596,6 +596,8 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/auth0.v5 v5.19.2 h1:GE7Xr+qcLO2ZkuGbaJfMv3VfWxhKoAQDD9P6q8ChMGM= gopkg.in/auth0.v5 v5.19.2/go.mod h1:ZUc29HB1p9iYkA1ti2uz/kVL3I9vg+Hs+qFjHKub9SM= +gopkg.in/auth0.v5 v5.20.0 h1:KytQF0ysIMrjK/AzlA2AYH44Zfv89qSrb2R7YzwQ/bM= +gopkg.in/auth0.v5 v5.20.0/go.mod h1:k1eJq1+II4rwUlecBabE7u4igEuzKUCEZAMa11PUfQk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From e19bbd7806976215ab746b59cd82c5b67d8f88ee Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Mon, 25 Oct 2021 10:26:51 +0200 Subject: [PATCH 02/12] Add auth0_organization resource --- auth0/provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/auth0/provider.go b/auth0/provider.go index e881da68..d4fe7c96 100644 --- a/auth0/provider.go +++ b/auth0/provider.go @@ -63,6 +63,7 @@ func init() { "auth0_log_stream": newLogStream(), "auth0_branding": newBranding(), "auth0_guardian": newGuardian(), + "auth0_organization": newOrganization(), }, ConfigureFunc: Configure, } From 0c2949e0c10b6746c915fe06c8b2bb1756e43258 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Mon, 25 Oct 2021 10:27:19 +0200 Subject: [PATCH 03/12] Add auth0_organization --- auth0/resource_auth0_organization.go | 142 ++++++++++++++++++++++ auth0/resource_auth0_organization_test.go | 64 ++++++++++ 2 files changed, 206 insertions(+) create mode 100644 auth0/resource_auth0_organization.go create mode 100644 auth0/resource_auth0_organization_test.go diff --git a/auth0/resource_auth0_organization.go b/auth0/resource_auth0_organization.go new file mode 100644 index 00000000..71d3e368 --- /dev/null +++ b/auth0/resource_auth0_organization.go @@ -0,0 +1,142 @@ +package auth0 + +import ( + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "gopkg.in/auth0.v5/management" +) + +func newOrganization() *schema.Resource { + return &schema.Resource{ + + Create: createOrganization, + Read: readOrganization, + Update: updateOrganization, + Delete: deleteOrganization, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of this organization", + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: "Friendly name of this organization", + }, + "branding": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + MinItems: 1, + Description: "Defines how to style the login pages", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "logo_url": { + Type: schema.TypeString, + Optional: true, + }, + "colors": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "metadata": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Metadata associated with the organization, Maximum of 10 metadata properties allowed", + }, + }, + } +} + +func createOrganization(d *schema.ResourceData, m interface{}) error { + o := expandOrganization(d) + api := m.(*management.Management) + if err := api.Organization.Create(o); err != nil { + return err + } + d.SetId(o.GetID()) + return readOrganization(d, m) +} + +func readOrganization(d *schema.ResourceData, m interface{}) error { + api := m.(*management.Management) + o, err := api.Organization.Read(d.Id()) + if err != nil { + if mErr, ok := err.(management.Error); ok { + if mErr.Status() == http.StatusNotFound { + d.SetId("") + return nil + } + } + return err + } + + d.SetId(o.GetID()) + d.Set("name", o.Name) + d.Set("display_name", o.DisplayName) + d.Set("branding", flattenOrganizationBranding(o.Branding)) + d.Set("metadata", o.Metadata) + + return nil +} + +func updateOrganization(d *schema.ResourceData, m interface{}) error { + o := expandOrganization(d) + api := m.(*management.Management) + err := api.Organization.Update(d.Id(), o) + if err != nil { + return err + } + return readOrganization(d, m) +} + +func deleteOrganization(d *schema.ResourceData, m interface{}) error { + api := m.(*management.Management) + err := api.Organization.Delete(d.Id()) + if err != nil { + if mErr, ok := err.(management.Error); ok { + if mErr.Status() == http.StatusNotFound { + d.SetId("") + return nil + } + } + } + return err +} + +func expandOrganization(d *schema.ResourceData) *management.Organization { + o := &management.Organization{ + Name: String(d, "name"), + DisplayName: String(d, "display_name"), + Metadata: Map(d, "metadata"), + } + List(d, "branding").Elem(func(d ResourceData) { + o.Branding = &management.OrganizationBranding{ + LogoURL: String(d, "logo_url"), + Colors: Map(d, "colors"), + } + }) + return o +} + +func flattenOrganizationBranding(b *management.OrganizationBranding) []interface{} { + m := make(map[string]interface{}) + if b != nil { + m["logo_url"] = b.LogoURL + m["colors"] = b.Colors + } + return []interface{}{m} +} diff --git a/auth0/resource_auth0_organization_test.go b/auth0/resource_auth0_organization_test.go new file mode 100644 index 00000000..85701af4 --- /dev/null +++ b/auth0/resource_auth0_organization_test.go @@ -0,0 +1,64 @@ +package auth0 + +import ( + "testing" + + "github.com/alexkappa/terraform-provider-auth0/auth0/internal/random" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccOrganization(t *testing.T) { + + rand := random.String(6) + + resource.Test(t, resource.TestCase{ + Providers: map[string]terraform.ResourceProvider{ + "auth0": Provider(), + }, + Steps: []resource.TestStep{ + { + Config: random.Template(testAccOrganizationCreate, rand), + Check: resource.ComposeTestCheckFunc( + random.TestCheckResourceAttr("auth0_organization.alexkappa", "name", "alexkappa", rand), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "display_name", "alexkappa.com"), + ), + }, + { + Config: random.Template(testAccOrganizationUpdate, rand), + Check: resource.ComposeTestCheckFunc( + random.TestCheckResourceAttr("auth0_organization.alexkappa", "name", "alexkappa", rand), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "display_name", "alexkappa.com"), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.#", "1"), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.logo_url", "https://alexkappa.com/logo.svg"), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.colors.%", "2"), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.colors.primary", "#e3e2f0"), + resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.colors.page_background", "#e3e2ff"), + ), + }, + }, + }) +} + +const testAccOrganizationCreate = ` + +resource auth0_organization alexkappa { + name = "alexkappa" + display_name = "alexkappa.com" +} +` + +const testAccOrganizationUpdate = ` + +resource auth0_organization alexkappa { + name = "alexkappa" + display_name = "alexkappa.com" + branding { + logo_url = "https://alexkappa.com/logo.svg" + colors = { + primary = "#e3e2f0" + page_background = "#e3e2ff" + } + } +} +` From 553d458f7e03ce2f4a8360515a95cb24217f8a9a Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Mon, 25 Oct 2021 10:41:34 +0200 Subject: [PATCH 04/12] Add docs for auth0_organization --- docs/resources/organization.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/resources/organization.md diff --git a/docs/resources/organization.md b/docs/resources/organization.md new file mode 100644 index 00000000..2ca70c2e --- /dev/null +++ b/docs/resources/organization.md @@ -0,0 +1,58 @@ +--- +layout: "auth0" +page_title: "Auth0: auth0_organization" +description: |- + The Organizations feature represents a broad update to the Auth0 platform that + allows our business-to-business (B2B) customers to better manage their partners + and customers, and to customize the ways that end-users access their + applications. Auth0 customers can use Organizations to: + + - Represent their business customers and partners in Auth0 and manage their + membership. + - Configure branded, federated login flows for each business. + - Build administration capabilities into their products, using Organizations + APIs, so that those businesses can manage their own organizations. +--- + +# auth0_organization + +The Organizations feature represents a broad update to the Auth0 platform that +allows our business-to-business (B2B) customers to better manage their partners +and customers, and to customize the ways that end-users access their +applications. Auth0 customers can use Organizations to: + + - Represent their business customers and partners in Auth0 and manage their + membership. + - Configure branded, federated login flows for each business. + - Build administration capabilities into their products, using Organizations + APIs, so that those businesses can manage their own organizations. + +## Example Usage + +```hcl +resource auth0_organization acme { + name = "acme" + display_name = "Acme Inc." + branding { + logo_url = "https://acme.com/logo.svg" + colors = { + primary = "#e3e2f0" + page_background = "#e3e2ff" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of this organization +* `display_name` – (Optional) Friendly name of this organization +* `branding` – (Optional) Defines how to style the login pages. For details, see [Branding](#branding) +* `metadata` - (Optional) Metadata associated with the organization, Maximum of 10 metadata properties allowed + +### Branding + +* `logo_url` - (Optional) URL of logo to display on login page +* `colors` - (Optional) Color scheme used to customize the login pages From c43fe6b23db24b4aebb5c1ddeb4a7838eb053f42 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Tue, 26 Oct 2021 16:56:14 +0200 Subject: [PATCH 05/12] Introduce connections set to auth0_organization --- auth0/resource_auth0_organization.go | 108 ++++++++++++++++++ auth0/resource_auth0_organization_test.go | 130 +++++++++++++++++++--- auth0/resource_auth0_role.go | 4 +- auth0/resource_auth0_user.go | 4 +- auth0/resource_data.go | 9 +- auth0/structure_auth0_connection.go | 2 +- 6 files changed, 230 insertions(+), 27 deletions(-) diff --git a/auth0/resource_auth0_organization.go b/auth0/resource_auth0_organization.go index 71d3e368..6767c6db 100644 --- a/auth0/resource_auth0_organization.go +++ b/auth0/resource_auth0_organization.go @@ -1,8 +1,11 @@ package auth0 import ( + "fmt" + "log" "net/http" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "gopkg.in/auth0.v5/management" ) @@ -57,6 +60,35 @@ func newOrganization() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Description: "Metadata associated with the organization, Maximum of 10 metadata properties allowed", }, + "connections": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connection_id": { + Type: schema.TypeString, + Required: true, + }, + "assign_membership_on_login": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + Set: func(v interface{}) int { + // The connection_id field determines the uniqueness of each + // item in the set. + m, ok := v.(map[string]interface{}) + if !ok { + return 0 + } + if v, ok := m["connection_id"].(string); ok { + return hashcode.String(v) + } + return 0 + }, + }, }, } } @@ -68,9 +100,62 @@ func createOrganization(d *schema.ResourceData, m interface{}) error { return err } d.SetId(o.GetID()) + + d.Partial(true) + err := assignOrganizationConnections(d, m) + if err != nil { + return fmt.Errorf("failed assigning organization connections. %w", err) + } + d.Partial(false) + return readOrganization(d, m) } +func assignOrganizationConnections(d *schema.ResourceData, m interface{}) (err error) { + + api := m.(*management.Management) + + add, rm := Diff(d, "connections") + + add.Elem(func(dd ResourceData) { + c := &management.OrganizationConnection{ + ConnectionID: String(dd, "connection_id"), + AssignMembershipOnLogin: Bool(dd, "assign_membership_on_login"), + } + log.Printf("[DEBUG] Connection (+) %s", c.GetConnectionID()) + err = api.Organization.AddConnection(d.Id(), c) + if err != nil { + return + } + }) + + rm.Elem(func(dd ResourceData) { + // Take connectionID before it changed (i.e. removed). Therefore we use + // GetChange() instead of the typical Get(). + connectionID, _ := dd.GetChange("connection_id") + log.Printf("[DEBUG] Connection (-) %s", connectionID.(string)) + err = api.Organization.DeleteConnection(d.Id(), connectionID.(string)) + if err != nil { + return + } + }) + + // Update existing connections if any mutable properties have changed. + Set(d, "connections", HasChange()).Elem(func(dd ResourceData) { + connectionID := dd.Get("connection_id").(string) + c := &management.OrganizationConnection{ + AssignMembershipOnLogin: Bool(dd, "assign_membership_on_login"), + } + log.Printf("[DEBUG] Connection (~) %s", connectionID) + err = api.Organization.UpdateConnection(d.Id(), connectionID, c) + if err != nil { + return + } + }) + + return nil +} + func readOrganization(d *schema.ResourceData, m interface{}) error { api := m.(*management.Management) o, err := api.Organization.Read(d.Id()) @@ -90,6 +175,21 @@ func readOrganization(d *schema.ResourceData, m interface{}) error { d.Set("branding", flattenOrganizationBranding(o.Branding)) d.Set("metadata", o.Metadata) + l, err := api.Organization.Connections(d.Id()) + if err != nil { + return err + } + + d.Set("connections", func() (v []interface{}) { + for _, connection := range l.OrganizationConnections { + v = append(v, &map[string]interface{}{ + "connection_id": connection.ConnectionID, + "assign_membership_on_login": connection.AssignMembershipOnLogin, + }) + } + return + }()) + return nil } @@ -100,6 +200,14 @@ func updateOrganization(d *schema.ResourceData, m interface{}) error { if err != nil { return err } + + d.Partial(true) + err = assignOrganizationConnections(d, m) + if err != nil { + return fmt.Errorf("failed updating organization connections. %w", err) + } + d.Partial(false) + return readOrganization(d, m) } diff --git a/auth0/resource_auth0_organization_test.go b/auth0/resource_auth0_organization_test.go index 85701af4..856d2980 100644 --- a/auth0/resource_auth0_organization_test.go +++ b/auth0/resource_auth0_organization_test.go @@ -1,13 +1,54 @@ package auth0 import ( + "log" + "strings" "testing" "github.com/alexkappa/terraform-provider-auth0/auth0/internal/random" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "gopkg.in/auth0.v5/management" ) +func init() { + resource.AddTestSweepers("auth0_organization", &resource.Sweeper{ + Name: "auth0_organization", + F: func(_ string) error { + api, err := Auth0() + if err != nil { + return err + } + var page int + for { + l, err := api.Organization.List(management.Page(page)) + if err != nil { + return err + } + for _, organization := range l.Organizations { + log.Printf("[DEBUG] ➝ %s", organization.GetName()) + if strings.Contains(organization.GetName(), "test") { + if e := api.Organization.Delete(organization.GetID()); e != nil { + multierror.Append(err, e) + } + log.Printf("[DEBUG] ✗ %s", organization.GetName()) + } + } + + if err != nil { + return err + } + if !l.HasNext() { + break + } + page++ + } + return nil + }, + }) +} + func TestAccOrganization(t *testing.T) { rand := random.String(6) @@ -20,45 +61,98 @@ func TestAccOrganization(t *testing.T) { { Config: random.Template(testAccOrganizationCreate, rand), Check: resource.ComposeTestCheckFunc( - random.TestCheckResourceAttr("auth0_organization.alexkappa", "name", "alexkappa", rand), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "display_name", "alexkappa.com"), + random.TestCheckResourceAttr("auth0_organization.acme", "name", "test-{{.random}}", rand), + random.TestCheckResourceAttr("auth0_organization.acme", "display_name", "Acme Inc. {{.random}}", rand), + resource.TestCheckResourceAttr("auth0_organization.acme", "connections.#", "1"), ), }, { Config: random.Template(testAccOrganizationUpdate, rand), Check: resource.ComposeTestCheckFunc( - random.TestCheckResourceAttr("auth0_organization.alexkappa", "name", "alexkappa", rand), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "display_name", "alexkappa.com"), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.#", "1"), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.logo_url", "https://alexkappa.com/logo.svg"), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.colors.%", "2"), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.colors.primary", "#e3e2f0"), - resource.TestCheckResourceAttr("auth0_organization.alexkappa", "branding.0.colors.page_background", "#e3e2ff"), + random.TestCheckResourceAttr("auth0_organization.acme", "name", "test-{{.random}}", rand), + random.TestCheckResourceAttr("auth0_organization.acme", "display_name", "Acme Inc. {{.random}}", rand), + resource.TestCheckResourceAttr("auth0_organization.acme", "branding.#", "1"), + resource.TestCheckResourceAttr("auth0_organization.acme", "branding.0.logo_url", "https://acme.com/logo.svg"), + resource.TestCheckResourceAttr("auth0_organization.acme", "branding.0.colors.%", "2"), + resource.TestCheckResourceAttr("auth0_organization.acme", "branding.0.colors.primary", "#e3e2f0"), + resource.TestCheckResourceAttr("auth0_organization.acme", "branding.0.colors.page_background", "#e3e2ff"), + resource.TestCheckResourceAttr("auth0_organization.acme", "connections.#", "2"), + ), + }, + { + Config: random.Template(testAccOrganizationUpdateAgain, rand), + Check: resource.ComposeTestCheckFunc( + random.TestCheckResourceAttr("auth0_organization.acme", "name", "test-{{.random}}", rand), + random.TestCheckResourceAttr("auth0_organization.acme", "display_name", "Acme Inc. {{.random}}", rand), + resource.TestCheckResourceAttr("auth0_organization.acme", "connections.#", "1"), ), }, }, }) } -const testAccOrganizationCreate = ` +const testAccOrganizationAux = ` + +resource auth0_connection acme { + name = "Acceptance-Test-Connection-Acme-{{.random}}" + strategy = "auth0" +} -resource auth0_organization alexkappa { - name = "alexkappa" - display_name = "alexkappa.com" +resource auth0_connection acmeinc { + name = "Acceptance-Test-Connection-Acme-Inc-{{.random}}" + strategy = "auth0" } ` -const testAccOrganizationUpdate = ` +const testAccOrganizationCreate = testAccOrganizationAux + ` + +resource auth0_organization acme { + name = "test-{{.random}}" + display_name = "Acme Inc. {{.random}}" + + connections { + connection_id = auth0_connection.acme.id + } +} +` -resource auth0_organization alexkappa { - name = "alexkappa" - display_name = "alexkappa.com" +const testAccOrganizationUpdate = testAccOrganizationAux + ` + +resource auth0_organization acme { + name = "test-{{.random}}" + display_name = "Acme Inc. {{.random}}" + branding { + logo_url = "https://acme.com/logo.svg" + colors = { + primary = "#e3e2f0" + page_background = "#e3e2ff" + } + } + connections { + connection_id = auth0_connection.acme.id + } + connections { + connection_id = auth0_connection.acmeinc.id + assign_membership_on_login = true + } +} +` + +const testAccOrganizationUpdateAgain = testAccOrganizationAux + ` + +resource auth0_organization acme { + name = "test-{{.random}}" + display_name = "Acme Inc. {{.random}}" branding { - logo_url = "https://alexkappa.com/logo.svg" + logo_url = "https://acme.com/logo.svg" colors = { primary = "#e3e2f0" page_background = "#e3e2ff" } } + connections { + connection_id = auth0_connection.acmeinc.id + assign_membership_on_login = false + } } ` diff --git a/auth0/resource_auth0_role.go b/auth0/resource_auth0_role.go index 5eb45b0c..c2642fba 100644 --- a/auth0/resource_auth0_role.go +++ b/auth0/resource_auth0_role.go @@ -161,7 +161,7 @@ func assignRolePermissions(d *schema.ResourceData, m interface{}) error { add, rm := Diff(d, "permissions") var addPermissions []*management.Permission - for _, addPermission := range add { + for _, addPermission := range add.List() { permission := addPermission.(map[string]interface{}) addPermissions = append(addPermissions, &management.Permission{ Name: auth0.String(permission["name"].(string)), @@ -170,7 +170,7 @@ func assignRolePermissions(d *schema.ResourceData, m interface{}) error { } var rmPermissions []*management.Permission - for _, rmPermission := range rm { + for _, rmPermission := range rm.List() { permission := rmPermission.(map[string]interface{}) rmPermissions = append(rmPermissions, &management.Permission{ Name: auth0.String(permission["name"].(string)), diff --git a/auth0/resource_auth0_user.go b/auth0/resource_auth0_user.go index 52d50a4f..46f8b26a 100644 --- a/auth0/resource_auth0_user.go +++ b/auth0/resource_auth0_user.go @@ -314,14 +314,14 @@ func assignUserRoles(d *schema.ResourceData, m interface{}) error { add, rm := Diff(d, "roles") var addRoles []*management.Role - for _, addRole := range add { + for _, addRole := range add.List() { addRoles = append(addRoles, &management.Role{ ID: auth0.String(addRole.(string)), }) } var rmRoles []*management.Role - for _, rmRole := range rm { + for _, rmRole := range rm.List() { rmRoles = append(rmRoles, &management.Role{ ID: auth0.String(rmRole.(string)), }) diff --git a/auth0/resource_data.go b/auth0/resource_data.go index a0f6c6ea..9a9ad943 100644 --- a/auth0/resource_data.go +++ b/auth0/resource_data.go @@ -339,14 +339,15 @@ func (s *set) List() []interface{} { // Diff accesses the value held by key and type asserts it to a set. It then // compares it's changes if any and returns what needs to be added and what // needs to be removed. -func Diff(d ResourceData, key string) (add []interface{}, rm []interface{}) { +func Diff(d ResourceData, key string) (add Iterator, rm Iterator) { if d.IsNewResource() { - add = Set(d, key).List() + add = Set(d, key) + rm = &set{newResourceDataAtKey(key, d), schema.NewSet(nil, []interface{}{})} } if d.HasChange(key) { o, n := d.GetChange(key) - add = n.(*schema.Set).Difference(o.(*schema.Set)).List() - rm = o.(*schema.Set).Difference(n.(*schema.Set)).List() + add = &set{newResourceDataAtKey(key, d), n.(*schema.Set).Difference(o.(*schema.Set))} + rm = &set{newResourceDataAtKey(key, d), o.(*schema.Set).Difference(n.(*schema.Set))} } return } diff --git a/auth0/structure_auth0_connection.go b/auth0/structure_auth0_connection.go index fa534780..d0419694 100644 --- a/auth0/structure_auth0_connection.go +++ b/auth0/structure_auth0_connection.go @@ -677,7 +677,7 @@ func expandConnectionOptionsScopes(d ResourceData, s scoper) { for _, scope := range add { s.SetScopes(true, scope.(string)) } - for _, scope := range rm { + for _, scope := range rm.List() { s.SetScopes(false, scope.(string)) } } From 7604088ebde3575b7c22273e1b016da8220c407d Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Wed, 27 Oct 2021 10:41:21 +0200 Subject: [PATCH 06/12] update changelog for 0.21.1 --- CHANGELOG.md | 5 ++--- auth0/resource_auth0_email_template_test.go | 2 +- auth0/resource_auth0_rule_test.go | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b6bb45..2cfbff7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,4 @@ -## Unreleased - -ENHANCEMENTS: +## 0.21.1 * resource/auth0_client: Documentation removal of `custom_login_page_preview` field [#386](https://github.com/alexkappa/terraform-provider-auth0/pull/386) * resource/auth0_client: Add `organization_usage` and `organization_require_behavior` parameters to `auth0_client` resource. ([#451](https://github.com/alexkappa/terraform-provider-auth0/pull/451)) @@ -8,6 +6,7 @@ ENHANCEMENTS: NOTES: * Bumped go-auth0 version to v5.17.0 [#398](https://github.com/alexkappa/terraform-provider-auth0/pull/398) +* Build darwin/arm64 binaries for Mac M1 silicon ([#421](https://github.com/alexkappa/terraform-provider-auth0/pull/421)) ## 0.21.0 diff --git a/auth0/resource_auth0_email_template_test.go b/auth0/resource_auth0_email_template_test.go index 8b209590..e581f01c 100644 --- a/auth0/resource_auth0_email_template_test.go +++ b/auth0/resource_auth0_email_template_test.go @@ -32,7 +32,7 @@ func TestAccEmailTemplate(t *testing.T) { "auth0": Provider(), }, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccEmailTemplateConfig, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("auth0_email_template.my_email_template", "template", "welcome_email"), diff --git a/auth0/resource_auth0_rule_test.go b/auth0/resource_auth0_rule_test.go index 1660254d..c0be0ae9 100644 --- a/auth0/resource_auth0_rule_test.go +++ b/auth0/resource_auth0_rule_test.go @@ -18,7 +18,7 @@ func TestAccRule(t *testing.T) { "auth0": Provider(), }, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: random.Template(testAccRule, rand), Check: resource.ComposeTestCheckFunc( random.TestCheckResourceAttr("auth0_rule.my_rule", "name", "acceptance-test-{{.random}}", rand), From 5b86f6f1be83d5e05961c354f3a38ef045e45862 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Wed, 27 Oct 2021 10:59:15 +0200 Subject: [PATCH 07/12] Update auth0_organization documentation --- docs/resources/organization.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/resources/organization.md b/docs/resources/organization.md index 2ca70c2e..8a3db376 100644 --- a/docs/resources/organization.md +++ b/docs/resources/organization.md @@ -40,6 +40,10 @@ resource auth0_organization acme { page_background = "#e3e2ff" } } + connections { + connection_id = auth0_connection.acme.id + assign_membership_on_login = true + } } ``` @@ -49,10 +53,23 @@ The following arguments are supported: * `name` - (Required) The name of this organization * `display_name` – (Optional) Friendly name of this organization -* `branding` – (Optional) Defines how to style the login pages. For details, see [Branding](#branding) -* `metadata` - (Optional) Metadata associated with the organization, Maximum of 10 metadata properties allowed +* `branding` – (Optional) Defines how to style the login pages. For details, see + [Branding](#branding) +* `metadata` - (Optional) Metadata associated with the organization, Maximum of + 10 metadata properties allowed +* `connections` – (Optional) Connections assigned to the organization. For + details, see [Connections](#connections) ### Branding * `logo_url` - (Optional) URL of logo to display on login page * `colors` - (Optional) Color scheme used to customize the login pages + +### Connections + +* `connection_id` – (Required) The connection ID of the connection to add to the + organization +* `assign_membership_on_login` – (Optional) When true, all users that log in + with this connection will be automatically granted membership in the + organization. When false, users must be granted membership in the organization + before logging in with this connection. From 38466d39860446919e2ee6215271771e5a7295e1 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Wed, 27 Oct 2021 11:03:02 +0200 Subject: [PATCH 08/12] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cfbff7d..5e43c2b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.21.2 + +* **New Resource:** `auth0_organization` ([#458](https://github.com/alexkappa/terraform-provider-auth0/pull/458)) + ## 0.21.1 * resource/auth0_client: Documentation removal of `custom_login_page_preview` field [#386](https://github.com/alexkappa/terraform-provider-auth0/pull/386) From f3894dea366293b9a6b6c9f0a2acae82196ca1d2 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Thu, 28 Oct 2021 09:42:00 +0200 Subject: [PATCH 09/12] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e43c2b0..f9f855c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.21.2 +## 0.22.0 * **New Resource:** `auth0_organization` ([#458](https://github.com/alexkappa/terraform-provider-auth0/pull/458)) From 5c8698153cae662c2b17a83155badeed7c7e1576 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Thu, 28 Oct 2021 10:12:02 +0200 Subject: [PATCH 10/12] Create hash package and use it in organization --- auth0/internal/hash/hash.go | 21 +++++++++++++++++++++ auth0/internal/hash/hash_test.go | 23 +++++++++++++++++++++++ auth0/resource_auth0_organization.go | 15 ++------------- 3 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 auth0/internal/hash/hash.go create mode 100644 auth0/internal/hash/hash_test.go diff --git a/auth0/internal/hash/hash.go b/auth0/internal/hash/hash.go new file mode 100644 index 00000000..36c3ee97 --- /dev/null +++ b/auth0/internal/hash/hash.go @@ -0,0 +1,21 @@ +package hash + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +// StringKey returns a schema.SchemaSetFunc able to hash a string value +// from map accessed by k. +func StringKey(k string) schema.SchemaSetFunc { + return func(v interface{}) int { + m, ok := v.(map[string]interface{}) + if !ok { + return 0 + } + if v, ok := m[k].(string); ok { + return hashcode.String(v) + } + return 0 + } +} diff --git a/auth0/internal/hash/hash_test.go b/auth0/internal/hash/hash_test.go new file mode 100644 index 00000000..c9ae95a8 --- /dev/null +++ b/auth0/internal/hash/hash_test.go @@ -0,0 +1,23 @@ +package hash + +import "testing" + +func TestStringKey(t *testing.T) { + + v := map[string]interface{}{ + "Foo": "Foo", + "Bar": "Bar", + } + + for key, expected := range map[string]int{ + "Foo": 3023971265, + "Bar": 1320340042, + } { + t.Run(key, func(t *testing.T) { + fn := StringKey(key) + if fn(v) != expected { + t.Errorf("expected %d to be %d", fn(v), expected) + } + }) + } +} diff --git a/auth0/resource_auth0_organization.go b/auth0/resource_auth0_organization.go index 6767c6db..3100f171 100644 --- a/auth0/resource_auth0_organization.go +++ b/auth0/resource_auth0_organization.go @@ -5,7 +5,7 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/alexkappa/terraform-provider-auth0/auth0/internal/hash" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "gopkg.in/auth0.v5/management" ) @@ -76,18 +76,7 @@ func newOrganization() *schema.Resource { }, }, }, - Set: func(v interface{}) int { - // The connection_id field determines the uniqueness of each - // item in the set. - m, ok := v.(map[string]interface{}) - if !ok { - return 0 - } - if v, ok := m["connection_id"].(string); ok { - return hashcode.String(v) - } - return 0 - }, + Set: hash.StringKey("connection_id"), }, }, } From 669b7416e99abf7841f832cb8569dc5c15565355 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Mon, 1 Nov 2021 09:32:23 +0100 Subject: [PATCH 11/12] Improve Diff so it doesn't return nil sets --- auth0/resource_data.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/auth0/resource_data.go b/auth0/resource_data.go index 9a9ad943..d3321e5c 100644 --- a/auth0/resource_data.go +++ b/auth0/resource_data.go @@ -340,10 +340,11 @@ func (s *set) List() []interface{} { // compares it's changes if any and returns what needs to be added and what // needs to be removed. func Diff(d ResourceData, key string) (add Iterator, rm Iterator) { - if d.IsNewResource() { - add = Set(d, key) - rm = &set{newResourceDataAtKey(key, d), schema.NewSet(nil, []interface{}{})} - } + // Zero the add and rm sets. These may be modified if the diff observed any + // changes. + add = &set{newResourceDataAtKey(key, d), d.Get(key).(*schema.Set)} + rm = &set{newResourceDataAtKey(key, d), &schema.Set{}} + if d.HasChange(key) { o, n := d.GetChange(key) add = &set{newResourceDataAtKey(key, d), n.(*schema.Set).Difference(o.(*schema.Set))} From 088d4b08e6c5b05978f6a0f481cb4db9da3f2e71 Mon Sep 17 00:00:00 2001 From: Alex Kalyvitis Date: Mon, 1 Nov 2021 10:01:30 +0100 Subject: [PATCH 12/12] Format organization connection logging --- auth0/resource_auth0_organization.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/auth0/resource_auth0_organization.go b/auth0/resource_auth0_organization.go index 3100f171..21259260 100644 --- a/auth0/resource_auth0_organization.go +++ b/auth0/resource_auth0_organization.go @@ -111,7 +111,7 @@ func assignOrganizationConnections(d *schema.ResourceData, m interface{}) (err e ConnectionID: String(dd, "connection_id"), AssignMembershipOnLogin: Bool(dd, "assign_membership_on_login"), } - log.Printf("[DEBUG] Connection (+) %s", c.GetConnectionID()) + log.Printf("[DEBUG] (+) auth0_organization.%s.connections.%s", d.Id(), c.GetConnectionID()) err = api.Organization.AddConnection(d.Id(), c) if err != nil { return @@ -122,7 +122,7 @@ func assignOrganizationConnections(d *schema.ResourceData, m interface{}) (err e // Take connectionID before it changed (i.e. removed). Therefore we use // GetChange() instead of the typical Get(). connectionID, _ := dd.GetChange("connection_id") - log.Printf("[DEBUG] Connection (-) %s", connectionID.(string)) + log.Printf("[DEBUG] (-) auth0_organization.%s.connections.%s", d.Id(), connectionID.(string)) err = api.Organization.DeleteConnection(d.Id(), connectionID.(string)) if err != nil { return @@ -135,7 +135,7 @@ func assignOrganizationConnections(d *schema.ResourceData, m interface{}) (err e c := &management.OrganizationConnection{ AssignMembershipOnLogin: Bool(dd, "assign_membership_on_login"), } - log.Printf("[DEBUG] Connection (~) %s", connectionID) + log.Printf("[DEBUG] (~) auth0_organization.%s.connections.%s", d.Id(), connectionID) err = api.Organization.UpdateConnection(d.Id(), connectionID, c) if err != nil { return