Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new resource to manage Organization Client Grants #1027

Merged
merged 15 commits into from
Oct 7, 2024
Merged
15 changes: 15 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,20 @@ issues:
- path: "(.+)_test.go"
linters:
- gosec
- path: "internal/go-vcr.v3"
linters:
- unused
- gofmt
- staticcheck
- revive
- godot
- whitespace
- goimports
- gosimple
- errcheck
- unconvert
- gocritic
- gosec
- gocyclo
exclude:
- "should have a package comment"
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"
duedares-rvj marked this conversation as resolved.
Show resolved Hide resolved
}


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 @@ -138,6 +138,7 @@ func New() *schema.Provider {
"auth0_user_role": user.NewRoleResource(),
"auth0_user_roles": user.NewRolesResource(),
"auth0_self_service_profile": selfserviceprofile.NewResource(),
"auth0_organization_client_grant": organization.NewOrganizationClientGrantResource(),
},
DataSourcesMap: map[string]*schema.Resource{
"auth0_attack_protection": attackprotection.NewDataSource(),
Expand Down
Loading
Loading