Skip to content

Commit

Permalink
DXCDT-428: User permissions resource (#578)
Browse files Browse the repository at this point in the history
* Initial commit for user permissions relationship resource

* Updating example

* Integrating into provider

* Re-recording test

* Custom doc templates for user permission and user permissions

* Adding update function, re-recording tests

* Stronger tests

---------

Co-authored-by: Will Vedder <will.vedder@okta.com>
  • Loading branch information
willvedd and willvedd authored May 12, 2023
1 parent da6394a commit f7ea5cb
Show file tree
Hide file tree
Showing 11 changed files with 3,726 additions and 467 deletions.
2 changes: 2 additions & 0 deletions docs/resources/user_permission.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ description: |-

With this resource, you can manage user permissions.

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

## Example Usage

```terraform
Expand Down
91 changes: 91 additions & 0 deletions docs/resources/user_permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
page_title: "Resource: auth0_user_permissions"
description: |-
With this resource, you can manage all of a user's permissions.
---

# Resource: auth0_user_permissions

With this resource, you can manage all of a user's permissions.

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

## Example Usage

```terraform
resource "auth0_resource_server" "resource_server" {
name = "Example Resource Server (Managed by Terraform)"
identifier = "https://api.example.com"
scopes {
value = "create:foo"
description = "Create foos"
}
scopes {
value = "read:foo"
description = "Read foos"
}
}
resource "auth0_user" "user" {
connection_name = "Username-Password-Authentication"
user_id = "12345"
username = "unique_username"
name = "Firstname Lastname"
nickname = "some.nickname"
email = "test@test.com"
email_verified = true
password = "passpass$12$12"
picture = "https://www.example.com/a-valid-picture-url.jpg"
}
resource "auth0_user_permissions" "all_user_permissions" {
user_id = auth0_user.user.id
permissions {
name = tolist(auth0_resource_server.resource_server.scopes)[0]
resource_server_identifier = auth0_resource_server.resource_server.identifier
}
permissions {
name = tolist(auth0_resource_server.resource_server.scopes)[1]
resource_server_identifier = auth0_resource_server.resource_server.identifier
}
}
```

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

### Required

- `permissions` (Block Set, Min: 1) List of API permissions granted to the user. (see [below for nested schema](#nestedblock--permissions))
- `user_id` (String) ID of the user to associate the permission to.

### Read-Only

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

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

Required:

- `name` (String) Name of permission.
- `resource_server_identifier` (String) Resource server identifier associated with the permission.

Read-Only:

- `description` (String) Description of the permission.
- `resource_server_name` (String) Name of resource server that the permission is associated with.

## Import

Import is supported using the following syntax:

```shell
# This resource can be imported by specifying the user ID

# Example:
terraform import auth0_user_permissions.all_user_permissions "auth0|111111111111111111111111"
```
4 changes: 4 additions & 0 deletions examples/resources/auth0_user_permissions/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This resource can be imported by specifying the user ID

# Example:
terraform import auth0_user_permissions.all_user_permissions "auth0|111111111111111111111111"
39 changes: 39 additions & 0 deletions examples/resources/auth0_user_permissions/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
resource "auth0_resource_server" "resource_server" {
name = "Example Resource Server (Managed by Terraform)"
identifier = "https://api.example.com"
scopes {
value = "create:foo"
description = "Create foos"
}

scopes {
value = "read:foo"
description = "Read foos"
}
}

resource "auth0_user" "user" {
connection_name = "Username-Password-Authentication"
user_id = "12345"
username = "unique_username"
name = "Firstname Lastname"
nickname = "some.nickname"
email = "test@test.com"
email_verified = true
password = "passpass$12$12"
picture = "https://www.example.com/a-valid-picture-url.jpg"
}

resource "auth0_user_permissions" "all_user_permissions" {
user_id = auth0_user.user.id

permissions {
name = tolist(auth0_resource_server.resource_server.scopes)[0]
resource_server_identifier = auth0_resource_server.resource_server.identifier
}

permissions {
name = tolist(auth0_resource_server.resource_server.scopes)[1]
resource_server_identifier = auth0_resource_server.resource_server.identifier
}
}
174 changes: 174 additions & 0 deletions internal/auth0/user/resource_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package user

import (
"context"
"net/http"

"github.com/auth0/go-auth0"
"github.com/auth0/go-auth0/management"
"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"
)

// NewPermissionsResource will return a new auth0_connection_client resource.
func NewPermissionsResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"user_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "ID of the user to associate the permission to.",
},
"permissions": {
Type: schema.TypeSet,
Required: true,
Description: "List of API permissions granted to the user.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Name of permission.",
},
"resource_server_identifier": {
Type: schema.TypeString,
Required: true,
Description: "Resource server identifier associated with the permission.",
},
"description": {
Type: schema.TypeString,
Computed: true,
Description: "Description of the permission.",
},
"resource_server_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of resource server that the permission is associated with.",
},
},
},
},
},
CreateContext: upsertUserPermissions,
UpdateContext: upsertUserPermissions,
ReadContext: readUserPermissions,
DeleteContext: deleteUserPermissions,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "With this resource, you can manage all of a user's permissions.",
}
}

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

userID := data.Get("user_id").(string)

if !data.HasChange("permissions") {
return nil
}

mutex.Lock(userID)
defer mutex.Unlock(userID)

toAdd, toRemove := value.Difference(data, "permissions")

var addPermissions []*management.Permission
for _, addPermission := range toAdd {
permission := addPermission.(map[string]interface{})
addPermissions = append(addPermissions, &management.Permission{
Name: auth0.String(permission["name"].(string)),
ResourceServerIdentifier: auth0.String(permission["resource_server_identifier"].(string)),
})
}

if len(addPermissions) > 0 {
if err := api.User.AssignPermissions(userID, addPermissions); err != nil {
return diag.FromErr(err)
}
}

var rmPermissions []*management.Permission
for _, rmPermission := range toRemove {
permission := rmPermission.(map[string]interface{})
rmPermissions = append(rmPermissions, &management.Permission{
Name: auth0.String(permission["name"].(string)),
ResourceServerIdentifier: auth0.String(permission["resource_server_identifier"].(string)),
})
}

if len(rmPermissions) > 0 {
if err := api.User.RemovePermissions(userID, rmPermissions); err != nil {
return diag.FromErr(err)
}
}

data.SetId(userID)

return readUserPermissions(ctx, data, meta)
}

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

userID := data.Get("user_id").(string)

permissions, err := api.User.Permissions(userID)
if err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}
return diag.FromErr(err)
}

err = data.Set("permissions", flattenUserPermissions(permissions))

return diag.FromErr(err)
}

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

userID := data.Get("user_id").(string)

mutex.Lock(userID)
defer mutex.Unlock(userID)

permissions, err := api.User.Permissions(userID)
if err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}
return diag.FromErr(err)
}

var rmPermissions []*management.Permission
for _, rmPermission := range permissions.Permissions {
rmPermissions = append(rmPermissions, &management.Permission{
Name: auth0.String(rmPermission.GetName()),
ResourceServerIdentifier: auth0.String(rmPermission.GetResourceServerIdentifier()),
})
}
if err := api.User.RemovePermissions(
userID,
rmPermissions,
); err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}
return diag.FromErr(err)
}

data.SetId("")
return nil
}
Loading

0 comments on commit f7ea5cb

Please sign in to comment.