Skip to content

Commit

Permalink
DXCDT-438: Add auth0_organization_connections resource (#610)
Browse files Browse the repository at this point in the history
Co-authored-by: Will Vedder <willvedd@gmail.com>
  • Loading branch information
sergiught and willvedd authored Jun 1, 2023
1 parent 063b7b7 commit 40afb14
Show file tree
Hide file tree
Showing 10 changed files with 5,896 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/resources/organization_connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ description: |-

With this resource, you can manage enabled connections on an organization.

!> To prevent issues, avoid using this resource together with the `auth0_organization_connections` resource.

## Example Usage

```terraform
Expand Down
78 changes: 78 additions & 0 deletions docs/resources/organization_connections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
page_title: "Resource: auth0_organization_connections"
description: |-
With this resource, you can manage enabled connections on an organization.
---

# Resource: auth0_organization_connections

With this resource, you can manage enabled connections on an organization.

!> To prevent issues, avoid using this resource together with the `auth0_organization_connection` resource.

## Example Usage

```terraform
resource "auth0_connection" "my_connection-1" {
name = "My Connection 1"
strategy = "auth0"
}
resource "auth0_connection" "my_connection-2" {
name = "My Connection 2"
strategy = "auth0"
}
resource "auth0_organization" "my_organization" {
name = "my-organization"
display_name = "My Organization"
}
resource "auth0_organization_connections" "one-to-many" {
organization_id = auth0_organization.my_organization.id
enabled_connections {
connection_id = auth0_connection.my_connection-1.id
assign_membership_on_login = true
}
enabled_connections {
connection_id = auth0_connection.my_connection-2.id
assign_membership_on_login = true
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `enabled_connections` (Block Set, Min: 1) Connections that are enabled for the organization. (see [below for nested schema](#nestedblock--enabled_connections))
- `organization_id` (String) ID of the organization on which to enable the connections.

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--enabled_connections"></a>
### Nested Schema for `enabled_connections`

Required:

- `connection_id` (String) The ID of the connection to enable for the organization.

Optional:

- `assign_membership_on_login` (Boolean) 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.

## Import

Import is supported using the following syntax:

```shell
# This resource can be imported by specifying the organization ID.
#
# Example:
terraform import auth0_organization_connections.my_org_conns org_XXXXX
```
4 changes: 4 additions & 0 deletions examples/resources/auth0_organization_connections/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This resource can be imported by specifying the organization ID.
#
# Example:
terraform import auth0_organization_connections.my_org_conns org_XXXXX
28 changes: 28 additions & 0 deletions examples/resources/auth0_organization_connections/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
resource "auth0_connection" "my_connection-1" {
name = "My Connection 1"
strategy = "auth0"
}

resource "auth0_connection" "my_connection-2" {
name = "My Connection 2"
strategy = "auth0"
}

resource "auth0_organization" "my_organization" {
name = "my-organization"
display_name = "My Organization"
}

resource "auth0_organization_connections" "one-to-many" {
organization_id = auth0_organization.my_organization.id

enabled_connections {
connection_id = auth0_connection.my_connection-1.id
assign_membership_on_login = true
}

enabled_connections {
connection_id = auth0_connection.my_connection-2.id
assign_membership_on_login = true
}
}
234 changes: 234 additions & 0 deletions internal/auth0/organization/resource_connections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package organization

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

"github.com/auth0/go-auth0"
"github.com/auth0/go-auth0/management"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/auth0/terraform-provider-auth0/internal/config"
"github.com/auth0/terraform-provider-auth0/internal/value"
)

// NewConnectionsResource will return a new auth0_organization_connections (1:many) resource.
func NewConnectionsResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"organization_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "ID of the organization on which to enable the connections.",
},
"enabled_connections": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"connection_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of the connection to enable for the organization.",
},
"assign_membership_on_login": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "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.",
},
},
},
Required: true,
Description: "Connections that are enabled for the organization.",
},
},
CreateContext: createOrganizationConnections,
ReadContext: readOrganizationConnections,
UpdateContext: updateOrganizationConnections,
DeleteContext: deleteOrganizationConnections,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "With this resource, you can manage enabled connections on an organization.",
}
}

func createOrganizationConnections(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()
mutex := meta.(*config.Config).GetMutex()

organizationID := data.Get("organization_id").(string)

mutex.Lock(organizationID)
defer mutex.Unlock(organizationID)

alreadyEnabledConnections, err := api.Organization.Connections(organizationID)
if err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}

return diag.FromErr(err)
}

data.SetId(organizationID)

connectionsToAdd := expandOrganizationConnections(data.GetRawConfig().GetAttr("enabled_connections"))

if diagnostics := guardAgainstErasingUnwantedConnections(
organizationID,
alreadyEnabledConnections.OrganizationConnections,
connectionsToAdd,
); diagnostics.HasError() {
data.SetId("")
return diagnostics
}

var result *multierror.Error
for _, connection := range connectionsToAdd {
if err := api.Organization.AddConnection(organizationID, connection); err != nil {
result = multierror.Append(result, err)
}
}

if result.ErrorOrNil() != nil {
return diag.FromErr(result.ErrorOrNil())
}

return readOrganizationConnections(ctx, data, meta)
}

func readOrganizationConnections(_ context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()

connections, err := api.Organization.Connections(data.Id())
if err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}

return diag.FromErr(err)
}

result := multierror.Append(
data.Set("organization_id", data.Id()),
data.Set("enabled_connections", flattenOrganizationConnections(connections.OrganizationConnections)),
)

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

func updateOrganizationConnections(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()
mutex := meta.(*config.Config).GetMutex()

orgID := data.Id()
mutex.Lock(orgID)
defer mutex.Unlock(orgID)

var result *multierror.Error
toAdd, toRemove := value.Difference(data, "enabled_connections")

for _, rmConnection := range toRemove {
connection := rmConnection.(map[string]interface{})

if err := api.Organization.DeleteConnection(orgID, connection["connection_id"].(string)); err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}

result = multierror.Append(result, err)
}
}

for _, addConnection := range toAdd {
connection := addConnection.(map[string]interface{})

if err := api.Organization.AddConnection(orgID, &management.OrganizationConnection{
ConnectionID: auth0.String(connection["connection_id"].(string)),
AssignMembershipOnLogin: auth0.Bool(connection["assign_membership_on_login"].(bool)),
}); err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}

result = multierror.Append(result, err)
}
}

if result.ErrorOrNil() != nil {
return diag.FromErr(result.ErrorOrNil())
}

return readOrganizationConnections(ctx, data, meta)
}

func deleteOrganizationConnections(_ context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()
mutex := meta.(*config.Config).GetMutex()
mutex.Lock(data.Id())
defer mutex.Unlock(data.Id())

var result *multierror.Error

connections := expandOrganizationConnections(data.GetRawState().GetAttr("enabled_connections"))
for _, conn := range connections {
err := api.Organization.DeleteConnection(data.Id(), conn.GetConnectionID())
result = multierror.Append(result, err)
}

if result.ErrorOrNil() != nil {
return diag.FromErr(result.ErrorOrNil())
}

data.SetId("")
return nil
}

func guardAgainstErasingUnwantedConnections(
organizationID string,
alreadyEnabledConnections []*management.OrganizationConnection,
connectionsToAdd []*management.OrganizationConnection,
) diag.Diagnostics {
if len(alreadyEnabledConnections) == 0 {
return nil
}

return diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Error,
Summary: "Organization with non empty enabled connections",
Detail: cmp.Diff(connectionsToAdd, alreadyEnabledConnections) +
fmt.Sprintf("\nThe organization already has enabled connections attached to it. "+
"Import the resource instead in order to proceed with the changes. "+
"Run: 'terraform import auth0_organization_connections.<given-name> %s'.", organizationID),
},
}
}

func expandOrganizationConnections(cfg cty.Value) []*management.OrganizationConnection {
connections := make([]*management.OrganizationConnection, 0)

cfg.ForEachElement(func(_ cty.Value, connectionCfg cty.Value) (stop bool) {
connections = append(connections, &management.OrganizationConnection{
ConnectionID: value.String(connectionCfg.GetAttr("connection_id")),
AssignMembershipOnLogin: value.Bool(connectionCfg.GetAttr("assign_membership_on_login")),
})

return stop
})

return connections
}
Loading

0 comments on commit 40afb14

Please sign in to comment.