Repository: mrparkers/terraform-provider-keycloak
Pull request #392 (2020-10-01 15:55:04)

Add the decision strategy setting to the openid client
This merge request is intended to allow overriding the decision strategy at the client level. If not implemented, the client always defines "Unanimuous" as the Global decision strategy, which can prevent some use caes to be imeplemented.


Patch:
diff --git a/docs/resources/openid_client.md b/docs/resources/openid_client.md
index 026a086..69d41d1 100644
--- a/docs/resources/openid_client.md
+++ b/docs/resources/openid_client.md
@@ -70,6 +70,7 @@ is set to `true`.
 - `exclude_session_state_from_auth_response` - (Optional) When `true`, the parameter `session_state` will not be included in OpenID Connect Authentication Response.
 - `authorization` - (Optional) When this block is present, fine-grained authorization will be enabled for this client. The client's `access_type` must be `CONFIDENTIAL`, and `service_accounts_enabled` must be `true`. This block has the following arguments:
     - `policy_enforcement_mode` - (Required) Dictates how policies are enforced when evaluating authorization requests. Can be one of `ENFORCING`, `PERMISSIVE`, or `DISABLED`.
+    - `decision_strategy` - (Optional) Dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. Could be one of `AFFIRMATIVE`, `CONSENSUS`, or `UNANIMOUS`. Applies to permissions.
     - `allow_remote_resource_management` - (Optional) When `true`, resources can be managed remotely by the resource server. Defaults to `false`.
     - `keep_defaults` - (Optional) When `true`, defaults set by Keycloak will be respected. Defaults to `false`.
 
diff --git a/docs/resources/openid_client_permission.md b/docs/resources/openid_client_permission.md
new file mode 100644
index 0000000..de7f25a
--- /dev/null
+++ b/docs/resources/openid_client_permission.md
@@ -0,0 +1,93 @@
+# keycloak_openid_client_permissions
+
+Allows you to manage all openid client Scope Based Permissions.
+
+This is part of a preview keycloak feature. You need to enable this feature to be able to use this resource.
+More information about enabling the preview feature can be found here: https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange
+
+When enabling Openid Client Permissions, Keycloak does several things automatically:
+1. Enable Authorization on build-in realm-management client
+1. Create scopes "view", "manage", "configure", "map-roles", "map-roles-client-scope", "map-roles-composite", "token-exchange"
+1. Create a resource representing the openid client
+1. Create all scope based permission for the scopes and openid client resource
+
+If the realm-management Authorization is not enable, you have to ceate a dependency (`depends_on`) with the policy and the openid client.
+
+### Example Usage
+
+```hcl
+resource "keycloak_realm" "realm" {
+	realm  = "realm"
+}
+
+resource "keycloak_openid_client" "my_openid_client" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "my_openid_client"
+  client_id             = "my_openid_client"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris   = [
+    "http://localhost:8080/*",
+  ]
+}
+
+data "keycloak_openid_client" "realm_management" {
+	realm_id  = keycloak_realm.realm.id
+	client_id = "realm-management"  
+  }
+
+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 {
+	resource_server_id = "${data.keycloak_openid_client.realm_management.id}"
+	realm_id = "${keycloak_realm.realm.id}"
+	name = "client_user_policy_test"
+	users = ["${keycloak_user.test.id}"]
+	logic = "POSITIVE"
+	decision_strategy = "UNANIMOUS"
+	depends_on = [
+		keycloak_openid_client.my_openid_client
+	]
+}
+
+resource "keycloak_openid_client_permissions" "my_permission" {
+	realm_id                               = keycloak_realm.realm.id
+	client_id                              = keycloak_openid_client.my_openid_client.id
+	view_scope_policy_id                   = keycloak_openid_client_user_policy.test.id
+	manage_scope_policy_id                 = keycloak_openid_client_user_policy.test.id
+	configure_scope_policy_id              = keycloak_openid_client_user_policy.test.id
+	map_roles_scope_policy_id              = keycloak_openid_client_user_policy.test.id
+	map_roles_client_scope_scope_policy_id = keycloak_openid_client_user_policy.test.id
+	map_roles_composite_scope_policy_id    = keycloak_openid_client_user_policy.test.id
+	token_exchange_scope_policy_id         = keycloak_openid_client_user_policy.test.id
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this group exists in.
+- `client_id` - (Required) The id of the client that provides the role.
+- `view_scope_policy_id` - (Optional) Policy id that will be set on the scope based view permission automatically created by enabling permissions on the reference openid client.
+- `manage_scope_policy_id` - (Optional) Policy id that will be set on the scope based manage permission automatically created by enabling permissions on the reference openid client.
+- `configure_scope_policy_id` - (Optional) Policy id that will be set on the scope based configure permission automatically created by enabling permissions on the reference openid client.
+- `map_roles_scope_policy_id` - (Optional) Policy id that will be set on the scope based map-roles permission automatically created by enabling permissions on the reference openid client.
+- `map_roles_client_scope_scope_policy_id` - (Optional) Policy id that will be set on the scope based map-roles-client-scope permission automatically created by enabling permissions on the reference openid client.
+- `map_roles_composite_scope_policy_id` - (Optional) Policy id that will be set on the scope based map-roles-composite permission automatically created by enabling permissions on the reference openid client.
+- `token_exchange_scope_policy_id` - (Optional) Policy id that will be set on the scope based token-exchange permission automatically created by enabling permissions on the reference openid client.
+
+### Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+- `authorization_resource_server_id` - Resource server id representing the realm management client on which this permission is managed.
+
diff --git a/keycloak/keycloak_client.go b/keycloak/keycloak_client.go
index cfc97d1..d914e72 100644
--- a/keycloak/keycloak_client.go
+++ b/keycloak/keycloak_client.go
@@ -2,7 +2,6 @@ package keycloak
 
 import (
 	"bytes"
-	"context"
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/json"
@@ -21,7 +20,6 @@ import (
 )
 
 type KeycloakClient struct {
-	ctx               context.Context
 	baseUrl           string
 	realm             string
 	clientCredentials *ClientCredentials
@@ -47,8 +45,6 @@ const (
 )
 
 func NewKeycloakClient(url, basePath, clientId, clientSecret, realm, username, password string, initialLogin bool, clientTimeout int, caCert string, tlsInsecureSkipVerify bool, userAgent string) (*KeycloakClient, error) {
-	ctx := context.Background()
-
 	cookieJar, err := cookiejar.New(&cookiejar.Options{
 		PublicSuffixList: publicsuffix.List,
 	})
@@ -91,7 +87,6 @@ func NewKeycloakClient(url, basePath, clientId, clientSecret, realm, username, p
 	}
 
 	keycloakClient := KeycloakClient{
-		ctx:               ctx,
 		baseUrl:           url + basePath,
 		clientCredentials: clientCredentials,
 		httpClient:        httpClient,
@@ -116,7 +111,11 @@ func (keycloakClient *KeycloakClient) login() error {
 
 	log.Printf("[DEBUG] Login request: %s", accessTokenData.Encode())
 
-	accessTokenRequest, _ := http.NewRequestWithContext(keycloakClient.ctx, http.MethodPost, accessTokenUrl, strings.NewReader(accessTokenData.Encode()))
+	accessTokenRequest, err := http.NewRequest(http.MethodPost, accessTokenUrl, strings.NewReader(accessTokenData.Encode()))
+	if err != nil {
+		return err
+	}
+
 	accessTokenRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
 	if keycloakClient.userAgent != "" {
@@ -157,7 +156,11 @@ func (keycloakClient *KeycloakClient) refresh() error {
 
 	log.Printf("[DEBUG] Refresh request: %s", refreshTokenData.Encode())
 
-	refreshTokenRequest, _ := http.NewRequestWithContext(keycloakClient.ctx, http.MethodPost, refreshTokenUrl, strings.NewReader(refreshTokenData.Encode()))
+	refreshTokenRequest, err := http.NewRequest(http.MethodPost, refreshTokenUrl, strings.NewReader(refreshTokenData.Encode()))
+	if err != nil {
+		return err
+	}
+
 	refreshTokenRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 
 	if keycloakClient.userAgent != "" {
@@ -331,7 +334,7 @@ func (keycloakClient *KeycloakClient) get(path string, resource interface{}, par
 func (keycloakClient *KeycloakClient) getRaw(path string, params map[string]string) ([]byte, error) {
 	resourceUrl := keycloakClient.baseUrl + apiUrl + path
 
-	request, err := http.NewRequestWithContext(keycloakClient.ctx, http.MethodGet, resourceUrl, nil)
+	request, err := http.NewRequest(http.MethodGet, resourceUrl, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -356,7 +359,7 @@ func (keycloakClient *KeycloakClient) post(path string, requestBody interface{})
 		return nil, "", err
 	}
 
-	request, err := http.NewRequestWithContext(keycloakClient.ctx, http.MethodPost, resourceUrl, bytes.NewReader(payload))
+	request, err := http.NewRequest(http.MethodPost, resourceUrl, bytes.NewReader(payload))
 	if err != nil {
 		return nil, "", err
 	}
@@ -374,7 +377,7 @@ func (keycloakClient *KeycloakClient) put(path string, requestBody interface{})
 		return err
 	}
 
-	request, err := http.NewRequestWithContext(keycloakClient.ctx, http.MethodPut, resourceUrl, bytes.NewReader(payload))
+	request, err := http.NewRequest(http.MethodPut, resourceUrl, bytes.NewReader(payload))
 	if err != nil {
 		return err
 	}
@@ -397,7 +400,7 @@ func (keycloakClient *KeycloakClient) delete(path string, requestBody interface{
 		body = bytes.NewReader(payload)
 	}
 
-	request, err := http.NewRequestWithContext(keycloakClient.ctx, http.MethodDelete, resourceUrl, body)
+	request, err := http.NewRequest(http.MethodDelete, resourceUrl, body)
 	if err != nil {
 		return err
 	}
diff --git a/keycloak/openid_client.go b/keycloak/openid_client.go
index aa8260b..2cb670f 100644
--- a/keycloak/openid_client.go
+++ b/keycloak/openid_client.go
@@ -20,6 +20,7 @@ type OpenidClientSecret struct {
 
 type OpenidClientAuthorizationSettings struct {
 	PolicyEnforcementMode         string `json:"policyEnforcementMode,omitempty"`
+	DecisionStrategy              string `json:"decisionStrategy,omitempty"`
 	AllowRemoteResourceManagement bool   `json:"allowRemoteResourceManagement,omitempty"`
 	KeepDefaults                  bool   `json:"-"`
 }
diff --git a/keycloak/openid_client_permissions.go b/keycloak/openid_client_permissions.go
new file mode 100644
index 0000000..c50fa83
--- /dev/null
+++ b/keycloak/openid_client_permissions.go
@@ -0,0 +1,38 @@
+package keycloak
+
+import (
+	"fmt"
+)
+
+type OpenidClientPermissionsInput struct {
+	Enabled bool `json:"enabled"`
+}
+
+type OpenidClientPermissions struct {
+	RealmId          string                 `json:"-"`
+	ClientId         string                 `json:"-"`
+	Enabled          bool                   `json:"enabled"`
+	Resource         string                 `json:"resource"`
+	ScopePermissions map[string]interface{} `json:"scopePermissions"`
+}
+
+func (keycloakClient *KeycloakClient) EnableOpenidClientPermissions(realmId, clientId string) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/clients/%s/management/permissions", realmId, clientId), OpenidClientPermissionsInput{Enabled: true})
+}
+
+func (keycloakClient *KeycloakClient) DisableOpenidClientPermissions(realmId, clientId string) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/clients/%s/management/permissions", realmId, clientId), OpenidClientPermissionsInput{Enabled: false})
+}
+
+func (keycloakClient *KeycloakClient) GetOpenidClientPermissions(realmId, clientId string) (*OpenidClientPermissions, error) {
+	var openidClientPermissions OpenidClientPermissions
+	openidClientPermissions.RealmId = realmId
+	openidClientPermissions.ClientId = clientId
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients/%s/management/permissions", realmId, clientId), &openidClientPermissions, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return &openidClientPermissions, nil
+}
diff --git a/provider/data_source_keycloak_openid_client.go b/provider/data_source_keycloak_openid_client.go
index 3249d9a..36eadb5 100644
--- a/provider/data_source_keycloak_openid_client.go
+++ b/provider/data_source_keycloak_openid_client.go
@@ -108,6 +108,10 @@ func dataSourceKeycloakOpenidClient() *schema.Resource {
 							Type:     schema.TypeString,
 							Computed: true,
 						},
+						"decision_strategy": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
 						"allow_remote_resource_management": {
 							Type:     schema.TypeBool,
 							Computed: true,
diff --git a/provider/provider.go b/provider/provider.go
index d8ca5a7..36bce2e 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -91,6 +91,7 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_authentication_execution":                          resourceKeycloakAuthenticationExecution(),
 			"keycloak_authentication_execution_config":                   resourceKeycloakAuthenticationExecutionConfig(),
 			"keycloak_identity_provider_token_exchange_scope_permission": resourceKeycloakIdentityProviderTokenExchangeScopePermission(),
+			"keycloak_openid_client_permissions":                         resourceKeycloakOpenidClientPermissions(),
 		},
 		Schema: map[string]*schema.Schema{
 			"client_id": {
diff --git a/provider/resource_keycloak_openid_client.go b/provider/resource_keycloak_openid_client.go
index 36a085c..65851cd 100644
--- a/provider/resource_keycloak_openid_client.go
+++ b/provider/resource_keycloak_openid_client.go
@@ -3,16 +3,18 @@ package provider
 import (
 	"errors"
 	"fmt"
+	"strings"
+
 	"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"
-	"strings"
 )
 
 var (
-	keycloakOpenidClientAccessTypes                        = []string{"CONFIDENTIAL", "PUBLIC", "BEARER-ONLY"}
-	keycloakOpenidClientAuthorizationPolicyEnforcementMode = []string{"ENFORCING", "PERMISSIVE", "DISABLED"}
-	keycloakOpenidClientPkceCodeChallengeMethod            = []string{"", "plain", "S256"}
+	keycloakOpenidClientAccessTypes                          = []string{"CONFIDENTIAL", "PUBLIC", "BEARER-ONLY"}
+	keycloakOpenidClientAuthorizationPolicyEnforcementMode   = []string{"ENFORCING", "PERMISSIVE", "DISABLED"}
+	keycloakOpenidClientResourcePermissionDecisionStrategies = []string{"UNANIMOUS", "AFFIRMATIVE", "CONSENSUS"}
+	keycloakOpenidClientPkceCodeChallengeMethod              = []string{"", "plain", "S256"}
 )
 
 func resourceKeycloakOpenidClient() *schema.Resource {
@@ -136,6 +138,12 @@ func resourceKeycloakOpenidClient() *schema.Resource {
 							Required:     true,
 							ValidateFunc: validation.StringInSlice(keycloakOpenidClientAuthorizationPolicyEnforcementMode, false),
 						},
+						"decision_strategy": {
+							Type:         schema.TypeString,
+							Optional:     true,
+							ValidateFunc: validation.StringInSlice(keycloakOpenidClientResourcePermissionDecisionStrategies, false),
+							Default:      "UNANIMOUS",
+						},
 						"allow_remote_resource_management": {
 							Type:     schema.TypeBool,
 							Optional: true,
@@ -274,6 +282,7 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient,
 		authorizationSettings := authorizationSettingsData.(map[string]interface{})
 		openidClient.AuthorizationSettings = &keycloak.OpenidClientAuthorizationSettings{
 			PolicyEnforcementMode:         authorizationSettings["policy_enforcement_mode"].(string),
+			DecisionStrategy:              authorizationSettings["decision_strategy"].(string),
 			AllowRemoteResourceManagement: authorizationSettings["allow_remote_resource_management"].(bool),
 			KeepDefaults:                  authorizationSettings["keep_defaults"].(bool),
 		}
diff --git a/provider/resource_keycloak_openid_client_authorization_permission.go b/provider/resource_keycloak_openid_client_authorization_permission.go
index 3c913e3..dda7415 100644
--- a/provider/resource_keycloak_openid_client_authorization_permission.go
+++ b/provider/resource_keycloak_openid_client_authorization_permission.go
@@ -10,8 +10,7 @@ import (
 )
 
 var (
-	keycloakOpenidClientResourcePermissionDecisionStrategies = []string{"UNANIMOUS", "AFFIRMATIVE", "CONSENSUS"}
-	keycloakOpenidClientPermissionTypes                      = []string{"resource", "scope"}
+	keycloakOpenidClientPermissionTypes = []string{"resource", "scope"}
 )
 
 func resourceKeycloakOpenidClientAuthorizationPermission() *schema.Resource {
diff --git a/provider/resource_keycloak_openid_client_permissions.go b/provider/resource_keycloak_openid_client_permissions.go
new file mode 100644
index 0000000..b264364
--- /dev/null
+++ b/provider/resource_keycloak_openid_client_permissions.go
@@ -0,0 +1,312 @@
+package provider
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func resourceKeycloakOpenidClientPermissions() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakOpenidClientPermissionsCreate,
+		Read:   resourceKeycloakOpenidClientPermissionsRead,
+		Delete: resourceKeycloakOpenidClientPermissionsDelete,
+		Update: resourceKeycloakOpenidClientPermissionsUpdate,
+		// This resource can be imported using {{realm}}/{{client_id}}. The Client ID is displayed in the GUI
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakOpenidClientPermissionsImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"client_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"enabled": {
+				Type:     schema.TypeBool,
+				Required: true,
+				ForceNew: 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_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"manage_scope_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"configure_scope_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"map_roles_scope_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"map_roles_client_scope_scope_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"map_roles_composite_scope_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"token_exchange_scope_policy_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+		},
+	}
+}
+
+func clientPermissionsId(realmId, clientId string) string {
+	return fmt.Sprintf("%s/%s", realmId, clientId)
+}
+
+func setOpenidClientScopePermissionPolicy(keycloakClient *keycloak.KeycloakClient, realmId, clientId string, scopeName string, policyId string) error {
+	openidClientPermissions, err := keycloakClient.GetOpenidClientPermissions(realmId, clientId)
+	if err != nil {
+		return err
+	}
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions[scopeName].(string))
+	if err != nil {
+		return err
+	}
+
+	permission.Policies = []string{policyId}
+
+	return keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
+}
+
+func unsetOpenidClientScopePermissionPolicy(keycloakClient *keycloak.KeycloakClient, realmId, clientId, scopeName string) error {
+	openidClientPermissions, err := keycloakClient.GetOpenidClientPermissions(realmId, clientId)
+	if err != nil {
+		return err
+	}
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions[scopeName].(string))
+	if err != nil {
+		return err
+	}
+
+	permission.Policies = []string{}
+	err = keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func resourceKeycloakOpenidClientPermissionsCreate(data *schema.ResourceData, meta interface{}) error {
+	return resourceKeycloakOpenidClientPermissionsUpdate(data, meta)
+}
+
+func resourceKeycloakOpenidClientPermissionsUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+
+	if data.Get("enabled").(bool) {
+		err := keycloakClient.EnableOpenidClientPermissions(realmId, clientId)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := keycloakClient.DisableOpenidClientPermissions(realmId, clientId)
+		if err != nil {
+			return err
+		}
+	}
+
+	viewScopePolicyId, ok := data.GetOkExists("view_scope_policy_id")
+	if ok && viewScopePolicyId != nil {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "view", viewScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+	manageScopePolicyId, ok := data.GetOkExists("manage_scope_policy_id")
+	if ok && manageScopePolicyId != "" {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "manage", manageScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+	configureScopePolicyId, ok := data.GetOkExists("configure_scope_policy_id")
+	if ok && configureScopePolicyId != "" {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "configure", configureScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+	mapRolesScopePolicyId, ok := data.GetOkExists("map_roles_scope_policy_id")
+	if ok && mapRolesScopePolicyId != "" {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "map-roles", mapRolesScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+	mapRolesClientsScopePolicyId, ok := data.GetOkExists("map_roles_client_scope_scope_policy_id")
+	if ok && mapRolesClientsScopePolicyId != "" {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "map-roles-client-scope", mapRolesClientsScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+	mapRolesCompositeScopePolicyId, ok := data.GetOkExists("map_roles_composite_scope_policy_id")
+	if ok && mapRolesCompositeScopePolicyId != "" {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "map-roles-composite", mapRolesCompositeScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+	tokenExchangeScopePolicyId, ok := data.GetOkExists("token_exchange_scope_policy_id")
+	if ok && tokenExchangeScopePolicyId != "" {
+		err := setOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "token-exchange", tokenExchangeScopePolicyId.(string))
+		if err != nil {
+			return err
+		}
+	}
+
+	return resourceKeycloakOpenidClientPermissionsRead(data, meta)
+}
+
+func resourceKeycloakOpenidClientPermissionsRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+
+	openidClientPermissions, err := keycloakClient.GetOpenidClientPermissions(realmId, clientId)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	data.SetId(clientPermissionsId(openidClientPermissions.RealmId, openidClientPermissions.ClientId))
+	data.Set("realm_id", openidClientPermissions.RealmId)
+	data.Set("client_id", openidClientPermissions.ClientId)
+
+	data.Set("enabled", openidClientPermissions.Enabled)
+
+	if !openidClientPermissions.Enabled {
+		log.Printf("[WARN] Removing resource with id %s from state as it no longer enabled", data.Id())
+		return nil
+	}
+
+	data.Set("view_scope_policy_id", nil)
+	data.Set("manage_scope_policy_id", nil)
+	data.Set("configure_scope_policy_id", nil)
+	data.Set("map_roles_scope_policy_id", nil)
+	data.Set("map_roles_client_scope_scope_policy_id", nil)
+	data.Set("map_roles_composite_scope_policy_id", nil)
+	data.Set("token_exchange_scope_policy_id", nil)
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+	permissionView, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["view"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionView != nil && len(permissionView.Policies) > 0 {
+		data.Set("view_scope_policy_id", permissionView.Policies[0])
+	}
+	permissionManage, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["manage"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionManage != nil && len(permissionManage.Policies) > 0 {
+		data.Set("manage_scope_policy_id", permissionManage.Policies[0])
+	}
+	permissionConfigure, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["configure"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionConfigure != nil && len(permissionConfigure.Policies) > 0 {
+		data.Set("configure_scope_policy_id", permissionConfigure.Policies[0])
+	}
+	permissionMapRoles, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["map-roles"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionMapRoles != nil && len(permissionMapRoles.Policies) > 0 {
+		data.Set("map_roles_scope_policy_id", permissionMapRoles.Policies[0])
+	}
+	permissionMapRolesClientScope, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["map-roles-client-scope"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionMapRolesClientScope != nil && len(permissionMapRolesClientScope.Policies) > 0 {
+		data.Set("map_roles_client_scope_scope_policy_id", permissionMapRolesClientScope.Policies[0])
+	}
+	permissionMapRolesComposite, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["map-roles-composite"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionMapRolesComposite != nil && len(permissionMapRolesComposite.Policies) > 0 {
+		data.Set("map_roles_composite_scope_policy_id", permissionMapRolesComposite.Policies[0])
+	}
+	permissionTokenExchange, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, openidClientPermissions.ScopePermissions["token-exchange"].(string))
+	if err != nil {
+		return err
+	}
+	if permissionTokenExchange != nil && len(permissionTokenExchange.Policies) > 0 {
+		data.Set("token_exchange_scope_policy_id", permissionTokenExchange.Policies[0])
+	}
+	data.Set("authorization_resource_server_id", realmManagementClient.Id)
+
+	return nil
+}
+
+func resourceKeycloakOpenidClientPermissionsDelete(data *schema.ResourceData, meta interface{}) error {
+
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+
+	openidClientPermissions, err := keycloakClient.GetOpenidClientPermissions(realmId, clientId)
+	if err == nil && openidClientPermissions.Enabled {
+		_ = unsetOpenidClientScopePermissionPolicy(keycloakClient, realmId, clientId, "view")
+	}
+	return keycloakClient.DisableOpenidClientPermissions(realmId, clientId)
+}
+
+func resourceKeycloakOpenidClientPermissionsImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+	parts := strings.Split(d.Id(), "/")
+	if len(parts) != 2 {
+		return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{openidClientId}}")
+	}
+	d.Set("realm_id", parts[0])
+	d.Set("client_id", parts[1])
+
+	d.SetId(clientPermissionsId(parts[0], parts[1]))
+
+	return []*schema.ResourceData{d}, nil
+}


Test patch:
diff --git a/provider/resource_keycloak_openid_client_permissions_test.go b/provider/resource_keycloak_openid_client_permissions_test.go
new file mode 100644
index 0000000..21afd70
--- /dev/null
+++ b/provider/resource_keycloak_openid_client_permissions_test.go
@@ -0,0 +1,243 @@
+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 TestAccKeycloakOpenidClientPermission_basic(t *testing.T) {
+	realmName := "tf_view-" + acctest.RandString(10)
+	clientId := "tf-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		ProviderFactories: testAccProviderFactories,
+		PreCheck:          func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenidClientPermission_basic(realmName, clientId),
+				Check:  testAccCheckKeycloakOpenidClientPermissionExists("keycloak_openid_client_permissions.my_permission"),
+			}, {
+				Config: testKeycloakOpenidClientPermissionDelete_basic(realmName, clientId),
+				Check:  testAccCheckKeycloakOpenidClientPermissionDoentExists("keycloak_openid_client_permissions.my_permission"),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakOpenidClientPermissionExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		permissions, err := getOpenidClientPermissionsFromState(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"]
+		viewScopePolicyId := rs.Primary.Attributes["view_scope_policy_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)
+		}
+
+		authzClient, err := keycloakClient.GetOpenidClientAuthorizationPermission(permissions.RealmId, realmManagementId, permissions.ScopePermissions["view"].(string))
+		if err != nil {
+			return err
+		}
+
+		policyId := authzClient.Policies[0]
+		if viewScopePolicyId != policyId {
+			return fmt.Errorf("computed ViewScopePolicyId %s was not equal to policyId %s", viewScopePolicyId, policyId)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakOpenidClientPermissionDoentExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		permissions, err := getOpenidClientPermissionsFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		rs, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return fmt.Errorf("resource not found: %s", resourceName)
+		}
+
+		if permissions.Enabled != false {
+			return fmt.Errorf("Client Permission in Keycloak is not disabled")
+		}
+		if rs.Primary.Attributes["enabled"] != "false" {
+			return fmt.Errorf("Client Permission State is not disabled")
+		}
+
+		return nil
+	}
+}
+
+func getOpenidClientPermissionsFromState(s *terraform.State, resourceName string) (*keycloak.OpenidClientPermissions, 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"]
+	clientId := rs.Primary.Attributes["client_id"]
+
+	permissions, err := keycloakClient.GetOpenidClientPermissions(realmId, clientId)
+	if err != nil {
+		return nil, fmt.Errorf("error getting openid_client permissions with realm id %s and client id %s: %s", realmId, clientId, err)
+
+	}
+	return permissions, nil
+}
+
+func testKeycloakOpenidClientPermission_basic(realmId, clientId string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+  realm = "%s"
+}
+
+resource keycloak_openid_client "my_openid_client" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "my_openid_client"
+  client_id             = "%s"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris   = [
+    "http://localhost:8080/*",
+  ]
+}
+
+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 {
+	resource_server_id = "${data.keycloak_openid_client.realm_management.id}"
+	realm_id = keycloak_realm.realm.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_permissions" "my_permission" {
+	realm_id                               = keycloak_realm.realm.id
+	client_id                              = keycloak_openid_client.my_openid_client.id
+
+	enabled = true
+
+	view_scope_policy_id                   = keycloak_openid_client_user_policy.test.id
+	manage_scope_policy_id                 = keycloak_openid_client_user_policy.test.id
+	configure_scope_policy_id              = keycloak_openid_client_user_policy.test.id
+	map_roles_scope_policy_id              = keycloak_openid_client_user_policy.test.id
+	map_roles_client_scope_scope_policy_id = keycloak_openid_client_user_policy.test.id
+	map_roles_composite_scope_policy_id    = keycloak_openid_client_user_policy.test.id
+	token_exchange_scope_policy_id         = keycloak_openid_client_user_policy.test.id
+}
+
+	`, realmId, clientId)
+}
+
+func testKeycloakOpenidClientPermissionDelete_basic(realmId, clientId string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+  realm = "%s"
+}
+
+resource keycloak_openid_client "my_openid_client" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "my_openid_client"
+  client_id             = "%s"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris   = [
+    "http://localhost:8080/*",
+  ]
+}
+
+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 {
+	resource_server_id = "${data.keycloak_openid_client.realm_management.id}"
+	realm_id = keycloak_realm.realm.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_permissions" "my_permission" {
+	realm_id                               = keycloak_realm.realm.id
+	client_id                              = keycloak_openid_client.my_openid_client.id
+
+	enabled = false
+}
+
+	`, realmId, clientId)
+}
diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go
index 0be2d34..82a9566 100644
--- a/provider/resource_keycloak_openid_client_test.go
+++ b/provider/resource_keycloak_openid_client_test.go
@@ -2,12 +2,13 @@ package provider
 
 import (
 	"fmt"
+	"regexp"
+	"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"
-	"regexp"
-	"testing"
 )
 
 func TestAccKeycloakOpenidClient_basic(t *testing.T) {