Repository: mrparkers/terraform-provider-keycloak
Pull request #400 (2020-10-21 08:43:35)

Add Keycloak_users_permission for admin_fine_grained_authz
Add resource keycloak_users_permission to manage the policies to apply to each scopes.

jermarchand: @mrparkers When this PR is OK for you and merged, I will correct PR #409 and make a new PR to correct PR #364

PS: if you think that my PR (#409 and #364) are compatible with the Hacktoberfest conditions, I will be happy that they are tagged "hacktoberfest-accepted" ;)
mrparkers: Hey @jermarchand, once those last comments are cleared up, I can get this merged, and you can work on the changes for #409 and #364 if you'd like.  If not, I can try to get to those when I have time.
mrparkers: Hey @jermarchand - I went ahead and pushed some changes to this PR.  If you're good with these changes, I can get this merged for you.  This LGTM as is.

Thanks again for your work on this!
jermarchand: @mrparkers go merge :) 

I will try to repot the changes in the other PRs :)


Patch:
diff --git a/docs/resources/users_permission.md b/docs/resources/users_permission.md
new file mode 100644
index 0000000..703e6d0
--- /dev/null
+++ b/docs/resources/users_permission.md
@@ -0,0 +1,144 @@
+---
+page_title: "keycloak_users_permissions Resource"
+---
+
+# keycloak_users_permissions
+
+Allows you to manage fine-grained permissions for all users in a realm: https://www.keycloak.org/docs/latest/server_admin/#_users-permissions
+
+This is part of a preview Keycloak feature: `admin_fine_grained_authz` (see https://www.keycloak.org/docs/latest/server_admin/#_fine_grain_permissions).
+This feature can be enabled with the Keycloak option `-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled`. See the
+example [`docker-compose.yml`](https://github.com/mrparkers/terraform-provider-keycloak/blob/898094df6b3e01c3404981ce7ca268142d6ff0e5/docker-compose.yml#L21) file for an example.
+
+When enabling fine-grained permissions for users, Keycloak does several things automatically:
+1. Enable Authorization on built-in `realm-management` client (if not already enabled).
+1. Create a resource representing the users permissions.
+1. Create scopes `view`, `manage`, `map-roles`, `manage-group-membership`, `impersonate`, and `user-impersonated`.
+1. Create all scope based permission for the scopes and users resources.
+
+~> This resource should only be created once per realm.
+
+## Example Usage
+
+```hcl
+resource "keycloak_realm" "realm" {
+  realm  = "my-realm"
+}
+
+data "keycloak_openid_client" "realm_management" {
+  realm_id  = keycloak_realm.realm.id
+  client_id = "realm-management"
+}
+
+// enable permissions for realm-management client
+resource "keycloak_openid_client_permissions" "realm_management_permission" {
+  realm_id   = keycloak_realm.realm.id
+  client_id  = data.keycloak_openid_client.realm_management.id
+  enabled    = true
+}
+
+// creating a user to use with the keycloak_openid_client_user_policy resource
+resource "keycloak_user" "test" {
+  realm_id = keycloak_realm.realm.id
+  username = "test-user"
+
+  email      = "test-user@fakedomain.com"
+  first_name = "Testy"
+  last_name  = "Tester"
+}
+
+resource "keycloak_openid_client_user_policy" "test" {
+  realm_id           = keycloak_realm.realm.id
+  resource_server_id = "${data.keycloak_openid_client.realm_management.id}"
+  name               = "client_user_policy_test"
+
+  users             = [keycloak_user.test.id]
+  logic             = "POSITIVE"
+  decision_strategy = "UNANIMOUS"
+
+  depends_on = [
+    keycloak_openid_client_permissions.realm-management_permission,
+  ]
+}
+
+resource "keycloak_users_permissions" "users_permissions" {
+  realm_id = keycloak_realm.realm.id
+
+  view_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "description"
+    decision_strategy = "UNANIMOUS"
+  }
+
+  manage_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "description"
+    decision_strategy = "UNANIMOUS"
+  }
+
+  map_roles_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "description"
+    decision_strategy = "UNANIMOUS"
+  }
+
+  manage_group_membership_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "description"
+    decision_strategy = "UNANIMOUS"
+  }
+
+  impersonate_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "description"
+    decision_strategy = "UNANIMOUS"
+  }
+
+  user_impersonated_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "description"
+    decision_strategy = "UNANIMOUS"
+  }
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm in which to manage fine-grained user permissions.
+
+Each of the scopes that can be managed are defined below:
+
+- `view_scope` - (Optional) When specified, set the scope based view permission.
+- `manage_scope` - (Optional) When specified, set the scope based manage permission.
+- `map_roles_scope` - (Optional) When specified, set the scope based map_roles permission.
+- `manage_group_membership_scope` - (Optional) When specified, set the scope based manage_group_membership permission.
+- `impersonate_scope` - (Optional) When specified, set the scope based impersonate permission.
+- `user_impersonated_scope` - (Optional) When specified, set the scope based user_impersonated permission.
+
+The configuration block for each of these scopes supports the following arguments:
+
+- `policies` - (Optional) Assigned policies to the permission. Each element within this list should be a policy ID.
+- `description` - (Optional) Description of the permission.
+- `decision_strategy` - (Optional) Decision strategy of the permission.
+
+### Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+- `enabled` - When true, this indicates that fine-grained user permissions are enabled. This will always be `true`.
+- `authorization_resource_server_id` - Resource server id representing the realm management client on which these permissions are managed.
+
diff --git a/example/client_authorization_policys.tf b/example/client_authorization_policys.tf
index d761923..44bb5c1 100644
--- a/example/client_authorization_policys.tf
+++ b/example/client_authorization_policys.tf
@@ -172,3 +172,25 @@ resource keycloak_openid_client_user_policy test {
   logic              = "POSITIVE"
   decision_strategy  = "UNANIMOUS"
 }
+
+# users permissions
+
+resource "keycloak_users_permissions" "my_permission" {
+  realm_id = keycloak_realm.test_authorization.id
+
+  view_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "view_scope"
+    decision_strategy = "CONSENSUS"
+  }
+
+  manage_scope {
+    policies          = [
+      keycloak_openid_client_user_policy.test.id
+    ]
+    description       = "manage_scope"
+    decision_strategy = "UNANIMOUS"
+  }
+}
diff --git a/keycloak/users_permissions.go b/keycloak/users_permissions.go
new file mode 100644
index 0000000..f1c4a19
--- /dev/null
+++ b/keycloak/users_permissions.go
@@ -0,0 +1,37 @@
+package keycloak
+
+import (
+	"fmt"
+)
+
+type UsersPermissionsInput struct {
+	Enabled bool `json:"enabled"`
+}
+
+type UsersPermissions struct {
+	RealmId          string                 `json:"-"`
+	Enabled          bool                   `json:"enabled"`
+	Resource         string                 `json:"resource"`
+	ScopePermissions map[string]interface{} `json:"scopePermissions"`
+}
+
+func (keycloakClient *KeycloakClient) EnableUsersPermissions(realmId string) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/users-management-permissions", realmId), UsersPermissionsInput{Enabled: true})
+}
+
+func (keycloakClient *KeycloakClient) DisableUsersPermissions(realmId string) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/users-management-permissions", realmId), UsersPermissionsInput{Enabled: false})
+}
+
+func (keycloakClient *KeycloakClient) GetUsersPermissions(realmId string) (*UsersPermissions, error) {
+	var openidClientPermissions UsersPermissions
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/users-management-permissions", realmId), &openidClientPermissions, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	openidClientPermissions.RealmId = realmId
+
+	return &openidClientPermissions, nil
+}
diff --git a/provider/provider.go b/provider/provider.go
index 36bce2e..b7b3509 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -92,6 +92,7 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_authentication_execution_config":                   resourceKeycloakAuthenticationExecutionConfig(),
 			"keycloak_identity_provider_token_exchange_scope_permission": resourceKeycloakIdentityProviderTokenExchangeScopePermission(),
 			"keycloak_openid_client_permissions":                         resourceKeycloakOpenidClientPermissions(),
+			"keycloak_users_permissions":                                 resourceKeycloakUsersPermissions(),
 		},
 		Schema: map[string]*schema.Schema{
 			"client_id": {
diff --git a/provider/resource_keycloak_users_permissions.go b/provider/resource_keycloak_users_permissions.go
new file mode 100644
index 0000000..384808e
--- /dev/null
+++ b/provider/resource_keycloak_users_permissions.go
@@ -0,0 +1,281 @@
+package provider
+
+import (
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func userScopePermissionsSchema() *schema.Schema {
+	return &schema.Schema{
+		Type:     schema.TypeSet,
+		Optional: true,
+		MaxItems: 1,
+		Elem: &schema.Resource{
+			Schema: map[string]*schema.Schema{
+				"policies": {
+					Type:     schema.TypeSet,
+					Elem:     &schema.Schema{Type: schema.TypeString},
+					Optional: true,
+				},
+				"description": {
+					Type:     schema.TypeString,
+					Optional: true,
+				},
+				"decision_strategy": {
+					Type:         schema.TypeString,
+					Optional:     true,
+					ValidateFunc: validation.StringInSlice(keycloakOpenidClientResourcePermissionDecisionStrategies, false),
+				},
+			},
+		},
+	}
+}
+
+func resourceKeycloakUsersPermissions() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakUsersPermissionsCreate,
+		Read:   resourceKeycloakUsersPermissionsRead,
+		Delete: resourceKeycloakUsersPermissionsDelete,
+		Update: resourceKeycloakUsersPermissionsUpdate,
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakUsersPermissionsImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"enabled": {
+				Type:     schema.TypeBool,
+				Computed: true,
+			},
+			"authorization_resource_server_id": {
+				Type:        schema.TypeString,
+				Computed:    true,
+				Description: "Resource server id representing the realm management client on which this permission is managed",
+			},
+			"view_scope":                    userScopePermissionsSchema(),
+			"manage_scope":                  userScopePermissionsSchema(),
+			"map_roles_scope":               userScopePermissionsSchema(),
+			"manage_group_membership_scope": userScopePermissionsSchema(),
+			"impersonate_scope":             userScopePermissionsSchema(),
+			"user_impersonated_scope":       userScopePermissionsSchema(),
+		},
+	}
+}
+
+func getUsersScopePermissions(keycloakClient *keycloak.KeycloakClient, realmId string, realmManagementClientId, permissionId string) (map[string]interface{}, error) {
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClientId, permissionId)
+	if err != nil {
+		return nil, err
+	}
+
+	if permission.Description == "" && permission.DecisionStrategy == "UNANIMOUS" && len(permission.Policies) == 0 {
+		return nil, nil
+	}
+
+	permissionViewSettings := make(map[string]interface{})
+
+	if permission.Description != "" {
+		permissionViewSettings["description"] = permission.Description
+	}
+
+	if permission.DecisionStrategy != "" {
+		permissionViewSettings["decision_strategy"] = permission.DecisionStrategy
+	}
+
+	if len(permission.Policies) > 0 {
+		permissionViewSettings["policies"] = permission.Policies
+	}
+
+	return permissionViewSettings, nil
+}
+
+func setUsersScopePermission(keycloakClient *keycloak.KeycloakClient, realmId, realmManagementClientId, authorizationPermissionId string, scopeDataSet *schema.Set) error {
+	var policies []string
+
+	scopeData := scopeDataSet.List()[0]
+	scopePermission := scopeData.(map[string]interface{})
+
+	if v, ok := scopePermission["policies"]; ok {
+		for _, policy := range v.(*schema.Set).List() {
+			policies = append(policies, policy.(string))
+		}
+	}
+
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClientId, authorizationPermissionId)
+	if err != nil {
+		return err
+	}
+
+	if v, ok := scopePermission["description"]; ok {
+		permission.Description = v.(string)
+	}
+
+	if v, ok := scopePermission["decision_strategy"]; ok {
+		permission.DecisionStrategy = v.(string)
+	}
+
+	permission.Policies = policies
+
+	return keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
+}
+
+func resourceKeycloakUsersPermissionsCreate(data *schema.ResourceData, meta interface{}) error {
+	return resourceKeycloakUsersPermissionsUpdate(data, meta)
+}
+
+func resourceKeycloakUsersPermissionsUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+
+	// the existence of this resource implies that it is enabled.
+	err := keycloakClient.EnableUsersPermissions(realmId)
+	if err != nil {
+		return err
+	}
+
+	// setting scope permissions requires us to fetch the users permissions details, as well as the realm management client
+	usersPermissions, err := keycloakClient.GetUsersPermissions(realmId)
+	if err != nil {
+		return err
+	}
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	viewScope, ok := data.GetOk("view_scope")
+	if ok {
+		err := setUsersScopePermission(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["view"].(string), viewScope.(*schema.Set))
+		if err != nil {
+			return err
+		}
+	}
+	manageScope, ok := data.GetOk("manage_scope")
+	if ok {
+		err := setUsersScopePermission(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["manage"].(string), manageScope.(*schema.Set))
+		if err != nil {
+			return err
+		}
+	}
+	mapRolesScope, ok := data.GetOk("map_roles_scope")
+	if ok {
+		err := setUsersScopePermission(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["map-roles"].(string), mapRolesScope.(*schema.Set))
+		if err != nil {
+			return err
+		}
+	}
+	manageGroupMembershipScope, ok := data.GetOk("manage_group_membership_scope")
+	if ok {
+		err := setUsersScopePermission(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["manage-group-membership"].(string), manageGroupMembershipScope.(*schema.Set))
+		if err != nil {
+			return err
+		}
+	}
+	impersonateScope, ok := data.GetOk("impersonate_scope")
+	if ok {
+		err := setUsersScopePermission(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["impersonate"].(string), impersonateScope.(*schema.Set))
+		if err != nil {
+			return err
+		}
+	}
+	userImpersonatedScope, ok := data.GetOk("user_impersonated_scope")
+	if ok {
+		err := setUsersScopePermission(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["user-impersonated"].(string), userImpersonatedScope.(*schema.Set))
+		if err != nil {
+			return err
+		}
+	}
+
+	return resourceKeycloakUsersPermissionsRead(data, meta)
+}
+
+func resourceKeycloakUsersPermissionsRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+	realmId := data.Get("realm_id").(string)
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	usersPermissions, err := keycloakClient.GetUsersPermissions(realmId)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	data.SetId(usersPermissions.RealmId)
+	data.Set("realm_id", usersPermissions.RealmId)
+	data.Set("enabled", usersPermissions.Enabled)
+	data.Set("authorization_resource_server_id", realmManagementClient.Id)
+
+	permissionView, err := getUsersScopePermissions(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["view"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionView != nil {
+		data.Set("view_scope", []interface{}{permissionView})
+	}
+
+	permissionManage, err := getUsersScopePermissions(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["manage"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionManage != nil {
+		data.Set("manage_scope", []interface{}{permissionManage})
+	}
+
+	permissionMapRoles, err := getUsersScopePermissions(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["map-roles"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionMapRoles != nil {
+		data.Set("map_roles_scope", []interface{}{permissionMapRoles})
+	}
+
+	permissionManageGroupMembership, err := getUsersScopePermissions(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["manage-group-membership"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionManageGroupMembership != nil {
+		data.Set("manage_group_membership_scope", []interface{}{permissionManageGroupMembership})
+	}
+
+	permissionImpersonate, err := getUsersScopePermissions(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["impersonate"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionImpersonate != nil {
+		data.Set("impersonate_scope", []interface{}{permissionImpersonate})
+	}
+
+	permissionUserImpersonated, err := getUsersScopePermissions(keycloakClient, realmId, realmManagementClient.Id, usersPermissions.ScopePermissions["user-impersonated"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionUserImpersonated != nil {
+		data.Set("user_impersonated_scope", []interface{}{permissionUserImpersonated})
+	}
+
+	return nil
+}
+
+func resourceKeycloakUsersPermissionsDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+
+	return keycloakClient.DisableUsersPermissions(realmId)
+}
+
+func resourceKeycloakUsersPermissionsImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+	d.Set("realm_id", d.Id())
+	d.SetId(d.Id())
+
+	return []*schema.ResourceData{d}, nil
+}


Test patch:
diff --git a/provider/resource_keycloak_users_permissions_test.go b/provider/resource_keycloak_users_permissions_test.go
new file mode 100644
index 0000000..a311247
--- /dev/null
+++ b/provider/resource_keycloak_users_permissions_test.go
@@ -0,0 +1,222 @@
+package provider
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func TestAccKeycloakUsersPermission_basic(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		ProviderFactories: testAccProviderFactories,
+		PreCheck:          func() { testAccPreCheck(t) },
+		CheckDestroy:      testAccCheckKeycloakUsersPermissionsAreDisabled(realmName),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakUsersPermission_basic(realmName),
+				Check:  testAccCheckKeycloakUsersPermissionExists("keycloak_users_permissions.my_permission"),
+			},
+			{
+				ResourceName:      "keycloak_users_permissions.my_permission",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateId:     realmName,
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakUsersPermissionExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		permissions, err := getUsersPermissionsFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+		rs, ok := s.RootModule().Resources[resourceName]
+
+		if !ok {
+			return fmt.Errorf("resource not found: %s", resourceName)
+		}
+		authorizationResourceServerId := rs.Primary.Attributes["authorization_resource_server_id"]
+
+		var realmManagementId string
+		clients, _ := keycloakClient.GetOpenidClients(permissions.RealmId, false)
+		for _, client := range clients {
+			if client.ClientId == "realm-management" {
+				realmManagementId = client.Id
+				break
+			}
+		}
+
+		if authorizationResourceServerId != realmManagementId {
+			return fmt.Errorf("computed authorizationResourceServerId %s was not equal to %s (the id of the realm-management client)", authorizationResourceServerId, realmManagementId)
+		}
+
+		viewScopePolicyId := rs.Primary.Attributes["view_scope.0.policies.0"]
+		viewScopeDescription := rs.Primary.Attributes["view_scope.0.description"]
+		viewScopeDecisionStrategy := rs.Primary.Attributes["view_scope.0.decision_strategy"]
+
+		authzClientView, err := keycloakClient.GetOpenidClientAuthorizationPermission(permissions.RealmId, realmManagementId, permissions.ScopePermissions["view"].(string))
+		if err != nil {
+			return err
+		}
+		policyId := authzClientView.Policies[0]
+
+		if viewScopePolicyId != policyId {
+			return fmt.Errorf("computed viewScopePolicyId %s was not equal to policyId %s", viewScopePolicyId, policyId)
+		}
+
+		if authzClientView.Description != viewScopeDescription {
+			return fmt.Errorf("DecisionStrategy %s was not equal to %s", authzClientView.DecisionStrategy, viewScopeDescription)
+		}
+
+		if authzClientView.DecisionStrategy != viewScopeDecisionStrategy {
+			return fmt.Errorf("DecisionStrategy %s was not equal to %s", authzClientView.DecisionStrategy, viewScopeDecisionStrategy)
+		}
+
+		authzClientManage, err := keycloakClient.GetOpenidClientAuthorizationPermission(permissions.RealmId, realmManagementId, permissions.ScopePermissions["manage"].(string))
+		if err != nil {
+			return err
+		}
+		policies := make([]interface{}, len(authzClientManage.Policies))
+		for i := range authzClientManage.Policies {
+			policies[i] = authzClientManage.Policies[i]
+		}
+
+		policyId = rs.Primary.Attributes["manage_scope.0.policies.0"]
+		if !Contains(policies, policyId) {
+			return fmt.Errorf("computed viewScopePolicyId %s was not equal to policyId %s", viewScopePolicyId, policyId)
+		}
+		policyId = rs.Primary.Attributes["manage_scope.0.policies.1"]
+		if !Contains(policies, policyId) {
+			return fmt.Errorf("computed viewScopePolicyId %s was not equal to policyId %s", viewScopePolicyId, policyId)
+		}
+
+		mapRolesScope := rs.Primary.Attributes["map_roles_scope"]
+
+		if mapRolesScope != "" {
+			return fmt.Errorf("map_roles_scope found")
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakUsersPermissionsAreDisabled(realmId string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		permissions, err := keycloakClient.GetUsersPermissions(realmId)
+		if err != nil {
+			return fmt.Errorf("error getting users permissions with realm id %s: %s", realmId, err)
+		}
+
+		if permissions.Enabled != false {
+			return fmt.Errorf("expected users permission in Keycloak to be disabled")
+		}
+
+		return nil
+	}
+}
+
+func getUsersPermissionsFromState(s *terraform.State, resourceName string) (*keycloak.UsersPermissions, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	realmId := rs.Primary.Attributes["realm_id"]
+
+	permissions, err := keycloakClient.GetUsersPermissions(realmId)
+	if err != nil {
+		return nil, fmt.Errorf("error getting users permissions with realm id %s: %s", realmId, err)
+
+	}
+	return permissions, nil
+}
+
+func testKeycloakUsersPermission_basic(realmId string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+data "keycloak_openid_client" "realm_management" {
+	realm_id  = keycloak_realm.realm.id
+	client_id = "realm-management"
+}
+
+resource "keycloak_openid_client_permissions" "realm-management_permission" {
+	realm_id   = keycloak_realm.realm.id
+	client_id  = data.keycloak_openid_client.realm_management.id
+	enabled    = true
+}
+
+resource "keycloak_user" "test" {
+	realm_id = keycloak_realm.realm.id
+	username = "test-user"
+
+	email      = "test-user@fakedomain.com"
+	first_name = "Testy"
+	last_name  = "Tester"
+}
+
+resource "keycloak_openid_client_user_policy" "test" {
+	realm_id           = keycloak_realm.realm.id
+	resource_server_id = data.keycloak_openid_client.realm_management.id
+	name               = "client_user_policy_test"
+
+	users             = [
+		keycloak_user.test.id
+	]
+	logic             = "POSITIVE"
+	decision_strategy = "UNANIMOUS"
+
+	depends_on = [
+		keycloak_openid_client_permissions.realm-management_permission,
+	]
+}
+resource "keycloak_openid_client_user_policy" "test2" {
+	realm_id           = keycloak_realm.realm.id
+	resource_server_id = data.keycloak_openid_client.realm_management.id
+	name               = "client_user_policy_test2"
+
+	users             = [
+		keycloak_user.test.id
+	]
+	logic             = "POSITIVE"
+	decision_strategy = "UNANIMOUS"
+
+	depends_on = [
+		keycloak_openid_client_permissions.realm-management_permission,
+	]
+}
+
+resource "keycloak_users_permissions" "my_permission" {
+	realm_id = keycloak_realm.realm.id
+
+	view_scope {
+		policies          = [
+			keycloak_openid_client_user_policy.test.id
+		]
+		description       = "view_scope"
+		decision_strategy = "CONSENSUS"
+	}
+
+	manage_scope {
+		policies          = [
+			keycloak_openid_client_user_policy.test.id,
+			keycloak_openid_client_user_policy.test2.id,
+		]
+		description       = "manage_scope"
+		decision_strategy = "UNANIMOUS"
+	}
+}
+	`, realmId)
+}