Skip to content

Commit

Permalink
Add new resource to manage Organization Client Grants (#1027)
Browse files Browse the repository at this point in the history
* Added new resource and data source for org client grants

* minor refactoring

* Added test case

* Updated test recordings

* Updated the test name

* Fix some timing issues in resourceserver, and add terraform version to server logs

* Remove unneeded lint config

* rebased

* minor refactoring

* Added test case

* Updated test recordings

* Updated the test name

* Fix some timing issues in resourceserver, and add terraform version to server logs

* Remove unneeded lint config

---------

Co-authored-by: A. Craig West <[email protected]>
  • Loading branch information
duedares-rvj and acwest authored Oct 7, 2024
1 parent 1a3b628 commit 46fe82e
Show file tree
Hide file tree
Showing 22 changed files with 14,674 additions and 8,219 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ test-unit: ## Run unit tests. To run a specific test, pass the FILTER var. Usage

test-acc: ## Run acceptance tests with http recordings. To run a specific test, pass the FILTER var. Usage `make test-acc FILTER="TestAccResourceServer`
${call print, "Running acceptance tests with http recordings"}
@AUTH0_HTTP_RECORDINGS=on \
@terraform version; \
AUTH0_HTTP_RECORDINGS=on \
AUTH0_DOMAIN=terraform-provider-auth0-dev.eu.auth0.com \
TF_ACC=1 \
go test \
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ data "auth0_organization" "some-organization-by-id" {
### Read-Only

- `branding` (List of Object) Defines how to style the login pages. (see [below for nested schema](#nestedatt--branding))
- `client_grants` (Set of String) Client Grant ID(s) that are associated to the organization.
- `connections` (Set of Object) (see [below for nested schema](#nestedatt--connections))
- `display_name` (String) Friendly name of this organization.
- `id` (String) The ID of this resource.
Expand Down
5 changes: 5 additions & 0 deletions docs/resources/client_grant.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ resource "auth0_client_grant" "my_client_grant" {
- `client_id` (String) ID of the client for this grant.
- `scopes` (List of String) Permissions (scopes) included in this grant.

### Optional

- `allow_any_organization` (Boolean) If enabled, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations.
- `organization_usage` (String) Defines whether organizations can be used with client credentials exchanges for this grant. (defaults to deny when not defined)

### Read-Only

- `id` (String) The ID of this resource.
Expand Down
25 changes: 25 additions & 0 deletions docs/resources/organization_client_grant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
page_title: "Resource: auth0_organization_client_grant"
description: |-
With this resource, you can manage a client grant associated with an organization.
---

# Resource: auth0_organization_client_grant

With this resource, you can manage a client grant associated with an organization.



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

### Required

- `grant_id` (String) A Client Grant ID to add to the organization.
- `organization_id` (String) The ID of the organization to associate the client grant.

### Read-Only

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


8 changes: 8 additions & 0 deletions internal/auth0/client/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,5 +945,13 @@ func expandClientGrant(data *schema.ResourceData) *management.ClientGrant {
clientGrant.Scope = value.Strings(cfg.GetAttr("scopes"))
}

if data.IsNewResource() || data.HasChange("allow_any_organization") {
clientGrant.AllowAnyOrganization = value.Bool(cfg.GetAttr("allow_any_organization"))
}

if data.IsNewResource() || data.HasChange("organization_usage") {
clientGrant.OrganizationUsage = value.String(cfg.GetAttr("organization_usage"))
}

return clientGrant
}
2 changes: 2 additions & 0 deletions internal/auth0/client/flatten.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,8 @@ func flattenClientGrant(data *schema.ResourceData, clientGrant *management.Clien
data.Set("client_id", clientGrant.GetClientID()),
data.Set("audience", clientGrant.GetAudience()),
data.Set("scopes", clientGrant.GetScope()),
data.Set("allow_any_organization", clientGrant.GetAllowAnyOrganization()),
data.Set("organization_usage", clientGrant.GetOrganizationUsage()),
)

return result.ErrorOrNil()
Expand Down
15 changes: 15 additions & 0 deletions internal/auth0/client/resource_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ func NewGrantResource() *schema.Resource {
Required: true,
Description: "Permissions (scopes) included in this grant.",
},
"organization_usage": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"allow", "deny", "require",
}, true),
Description: "Defines whether organizations can be used with client credentials exchanges " +
"for this grant. (defaults to deny when not defined)",
},
"allow_any_organization": {
Type: schema.TypeBool,
Optional: true,
Description: "If enabled, any organization can be used with this grant. If disabled (default), " +
"the grant must be explicitly assigned to the desired organizations.",
},
},
}
}
Expand Down
29 changes: 28 additions & 1 deletion internal/auth0/organization/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ func dataSourceSchema() map[string]*schema.Schema {
Description: "User ID(s) that are members of the organization.",
}

dataSourceSchema["client_grants"] = &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
Description: "Client Grant ID(s) that are associated to the organization.",
}

return dataSourceSchema
}

Expand All @@ -101,7 +110,12 @@ func readOrganizationForDataSource(ctx context.Context, data *schema.ResourceDat
return diag.FromErr(err)
}

return diag.FromErr(flattenOrganizationForDataSource(data, foundOrganization, foundConnections, foundMembers))
foundClientGrants, err := fetchAllOrganizationClientGrants(ctx, api, foundOrganization.GetID())
if err != nil {
return diag.FromErr(err)
}

return diag.FromErr(flattenOrganizationForDataSource(data, foundOrganization, foundConnections, foundMembers, foundClientGrants))
}

func findOrganizationByIDOrName(
Expand Down Expand Up @@ -173,3 +187,16 @@ func fetchAllOrganizationMembers(

return foundMembers, nil
}

func fetchAllOrganizationClientGrants(
ctx context.Context,
api *management.Management,
organizationID string,
) ([]*management.ClientGrant, error) {
clientGrantList, err := api.Organization.ClientGrants(ctx, organizationID)
if err != nil {
return nil, err
}

return clientGrantList.ClientGrants, nil
}
13 changes: 13 additions & 0 deletions internal/auth0/organization/flatten.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ func flattenOrganizationForDataSource(
organization *management.Organization,
connections []*management.OrganizationConnection,
members []management.OrganizationMember,
clientGrants []*management.ClientGrant,
) error {
result := multierror.Append(
flattenOrganization(data, organization),
data.Set("connections", flattenOrganizationEnabledConnections(connections)),
data.Set("members", flattenOrganizationMembersSlice(members)),
data.Set("client_grants", flattenOrganizationClientGrantsSlice(clientGrants)),
)

return result.ErrorOrNil()
Expand Down Expand Up @@ -113,3 +115,14 @@ func flattenOrganizationMembersSlice(members []management.OrganizationMember) []

return flattenedMembers
}

func flattenOrganizationClientGrantsSlice(clientGrants []*management.ClientGrant) []string {
if len(clientGrants) == 0 {
return nil
}
flattenedClientGrants := make([]string, 0)
for _, grant := range clientGrants {
flattenedClientGrants = append(flattenedClientGrants, grant.GetID())
}
return flattenedClientGrants
}
87 changes: 87 additions & 0 deletions internal/auth0/organization/resource_client_grant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package organization

import (
"context"

"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"
internalError "github.com/auth0/terraform-provider-auth0/internal/error"
internalSchema "github.com/auth0/terraform-provider-auth0/internal/schema"
)

// NewOrganizationClientGrantResource will return a new auth0_organization_client_grant resource.
func NewOrganizationClientGrantResource() *schema.Resource {
return &schema.Resource{
Description: "With this resource, you can manage a client grant associated with an organization.",
CreateContext: createOrganizationClientGrant,
ReadContext: readOrganizationClientGrant,
DeleteContext: deleteOrganizationClientGrant,
Importer: &schema.ResourceImporter{
StateContext: internalSchema.ImportResourceGroupID("organization_id", "grant_id"),
},
Schema: map[string]*schema.Schema{
"organization_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The ID of the organization to associate the client grant.",
},
"grant_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "A Client Grant ID to add to the organization.",
},
},
}
}

func createOrganizationClientGrant(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*config.Config).GetAPI()
organizationID := data.Get("organization_id").(string)
grantID := data.Get("grant_id").(string)

if err := api.Organization.AssociateClientGrant(ctx, organizationID, grantID); err != nil {
return diag.FromErr(err)
}

internalSchema.SetResourceGroupID(data, organizationID, grantID)

return readOrganizationClientGrant(ctx, data, meta)
}

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

organizationID := data.Get("organization_id").(string)
clientGrantList, err := api.Organization.ClientGrants(ctx, organizationID)

if err != nil {
return diag.FromErr(internalError.HandleAPIError(data, err))
}

grantID := data.Get("grant_id").(string)
for _, grant := range clientGrantList.ClientGrants {
if grant.GetID() == grantID {
return nil
}
}

data.SetId("")
return nil
}

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

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

if err := api.Organization.RemoveClientGrant(ctx, organizationID, grantID); err != nil {
return diag.FromErr(internalError.HandleAPIError(data, err))
}

return nil
}
70 changes: 70 additions & 0 deletions internal/auth0/organization/resource_client_grant_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package organization_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"

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

const testAssociateOrganizationClientGrant = `
resource "auth0_organization" "my_organization" {
name = "test-org-acceptance-testing"
display_name = "Test Org Acceptance Testing"
}
resource "auth0_resource_server" "new_resource_server" {
depends_on = [ auth0_organization.my_organization ]
name = "Example API"
identifier = "https://api.travel00123.com/"
}
resource "auth0_client" "my_test_client"{
depends_on = [ auth0_organization.my_organization, auth0_resource_server.new_resource_server ]
name = "test_client"
organization_usage = "allow"
default_organization {
organization_id = auth0_organization.my_organization.id
flows = ["client_credentials"]
}
}
resource "auth0_client_grant" "my_client_grant" {
depends_on = [ auth0_resource_server.new_resource_server, auth0_client.my_test_client ]
client_id = auth0_client.my_test_client.id
audience = auth0_resource_server.new_resource_server.identifier
scopes = ["create:organization_client_grants","create:resource"]
allow_any_organization = true
organization_usage = "allow"
}
resource "auth0_organization_client_grant" "associate_org_client_grant"{
depends_on = [ auth0_client_grant.my_client_grant ]
organization_id = auth0_organization.my_organization.id
grant_id = auth0_client_grant.my_client_grant.id
}
data "auth0_organization" "retrieve_org_data" {
depends_on = [ auth0_organization_client_grant.associate_org_client_grant ]
organization_id = auth0_organization.my_organization.id
}
`

func TestAccOrganizationClientGrant(t *testing.T) {
acctest.Test(t, resource.TestCase{
Steps: []resource.TestStep{
{
Config: acctest.ParseTestName(testAssociateOrganizationClientGrant, t.Name()),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("auth0_organization_client_grant.associate_org_client_grant", "organization_id"),
resource.TestCheckResourceAttrSet("auth0_organization_client_grant.associate_org_client_grant", "grant_id"),
resource.TestCheckResourceAttrSet("data.auth0_organization.retrieve_org_data", "client_grants.0"),
),
},
},
})
}
3 changes: 3 additions & 0 deletions internal/auth0/resourceserver/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"time"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/go-multierror"
Expand Down Expand Up @@ -270,6 +271,7 @@ func createResourceServer(ctx context.Context, data *schema.ResourceData, meta i
if err := fixNullableAttributes(ctx, data, api); err != nil {
return diag.FromErr(err)
}
time.Sleep(200 * time.Millisecond)

return readResourceServer(ctx, data, meta)
}
Expand All @@ -286,6 +288,7 @@ func updateResourceServer(ctx context.Context, data *schema.ResourceData, meta i
if err := fixNullableAttributes(ctx, data, api); err != nil {
return diag.FromErr(err)
}
time.Sleep(200 * time.Millisecond)

return readResourceServer(ctx, data, meta)
}
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func New() *schema.Provider {
"auth0_hook": hook.NewResource(),
"auth0_log_stream": logstream.NewResource(),
"auth0_organization": organization.NewResource(),
"auth0_organization_client_grant": organization.NewOrganizationClientGrantResource(),
"auth0_organization_connection": organization.NewConnectionResource(),
"auth0_organization_connections": organization.NewConnectionsResource(),
"auth0_organization_member": organization.NewMemberResource(),
Expand Down
Loading

0 comments on commit 46fe82e

Please sign in to comment.