Repository: mrparkers/terraform-provider-keycloak
Pull request #711 (2022-07-25 15:05:02)

Fix remember me docs
https://github.com/mrparkers/terraform-provider-keycloak/pull/374/files added support for these params, but missed the docs.

This closes https://github.com/mrparkers/terraform-provider-keycloak/issues/664

bplotnick-humane: This also closes https://github.com/mrparkers/terraform-provider-keycloak/issues/402

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54e9a4c..b41c0f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+## v3.10.0 (July 28, 2022)
+
+IMPROVEMENTS:
+
+- add authn context attributes for `keycloak_saml_identity_provider` resource ([#703](https://github.com/mrparkers/terraform-provider-keycloak/pull/703))
+- add `resource_type` attribute for `keycloak_openid_client_authorization_permission` resource ([#702](https://github.com/mrparkers/terraform-provider-keycloak/pull/702))
+
+Huge thanks to all the individuals who have contributed towards this release:
+
+- [@dnic](https://github.com/dnic)
+- [@1337andre](https://github.com/1337andre)
+- [@JessieAMorris](https://github.com/JessieAMorris)
+
 ## v3.9.1 (July 11, 2022)
 
 BUG FIXES:
diff --git a/docs/resources/default_roles.md b/docs/resources/default_roles.md
index b72dfc6..91e7c77 100644
--- a/docs/resources/default_roles.md
+++ b/docs/resources/default_roles.md
@@ -16,7 +16,7 @@ resource "keycloak_realm" "realm" {
   enabled = true
 }
 
-resource "keycloak_default_roles" "defalut_roles" {
+resource "keycloak_default_roles" "default_roles" {
   realm_id      = keycloak_realm.realm.id
   default_roles = ["uma_authorization"]
 }
diff --git a/docs/resources/ldap_hardcoded_attribute_mapper.md b/docs/resources/ldap_hardcoded_attribute_mapper.md
new file mode 100644
index 0000000..35ca8df
--- /dev/null
+++ b/docs/resources/ldap_hardcoded_attribute_mapper.md
@@ -0,0 +1,67 @@
+---
+page_title: "keycloak_ldap_hardcoded_attribute_mapper Resource"
+---
+
+# keycloak_ldap_hardcoded_attribute_mapper Resource
+
+Allows for creating and managing hardcoded attribute mappers for Keycloak users federated via LDAP.
+
+The LDAP hardcoded attribute mapper will set the specified value to the LDAP attribute.
+
+**NOTE**: This mapper only works when the `sync_registrations` attribute on the `keycloak_ldap_user_federation` resource is set to `true`.
+
+## Example Usage
+
+```hcl
+resource "keycloak_realm" "realm" {
+  realm   = "my-realm"
+  enabled = true
+}
+
+resource "keycloak_ldap_user_federation" "ldap_user_federation" {
+  name     = "openldap"
+  realm_id = keycloak_realm.realm.id
+
+  username_ldap_attribute = "cn"
+  rdn_ldap_attribute      = "cn"
+  uuid_ldap_attribute     = "entryDN"
+  user_object_classes     = [
+    "simpleSecurityObject",
+    "organizationalRole"
+  ]
+
+  connection_url  = "ldap://openldap"
+  users_dn        = "dc=example,dc=org"
+  bind_dn         = "cn=admin,dc=example,dc=org"
+  bind_credential = "admin"
+
+  sync_registrations = true
+}
+
+resource "keycloak_ldap_hardcoded_attribute_mapper" "assign_bar_to_foo" {
+  realm_id                = keycloak_realm.realm.id
+  ldap_user_federation_id = keycloak_ldap_user_federation.ldap_user_federation.id
+  name                    = "assign-foo-to-bar"
+  attribute_name          = "foo"
+  attribute_value         = "bar"
+}
+```
+
+## Argument Reference
+
+-   `realm_id` - (Required) The realm that this LDAP mapper will exist in.
+-   `ldap_user_federation_id` - (Required) The ID of the LDAP user federation provider to attach this mapper to.
+-   `name` - (Required) Display name of this mapper when displayed in the console.
+-   `attribute_name` - (Required) The name of the LDAP attribute to set.
+-   `attribute_value` - (Required) The value to set to the LDAP attribute. You can hardcode any value like 'foo'.
+
+## Import
+
+LDAP mappers can be imported using the format `{{realm_id}}/{{ldap_user_federation_id}}/{{ldap_mapper_id}}`.
+The ID of the LDAP user federation provider and the mapper can be found within the Keycloak GUI, and they are typically GUIDs.
+
+Example:
+
+```bash
+$ terraform import keycloak_ldap_hardcoded_attribute_mapper.assign_bar_to_foo my-realm/af2a6ca3-e4d7-49c3-b08b-1b3c70b4b860/3d923ece-1a91-4bf7-adaf-3b82f2a12b67
+```
diff --git a/docs/resources/openid_client_authorization_permission.md b/docs/resources/openid_client_authorization_permission.md
new file mode 100644
index 0000000..c22ed4e
--- /dev/null
+++ b/docs/resources/openid_client_authorization_permission.md
@@ -0,0 +1,84 @@
+# keycloak_openid_client_authorization_permission
+
+Allows you to manage openid Client Authorization Permissions.
+
+### Example Usage
+
+```hcl
+resource "keycloak_realm" "realm" {
+	realm   = "my-realm"
+	enabled = true
+}
+
+resource keycloak_openid_client test {
+	client_id                = "client_id"
+	realm_id                 = keycloak_realm.realm.id
+	access_type              = "CONFIDENTIAL"
+	service_accounts_enabled = true
+	authorization {
+		policy_enforcement_mode = "ENFORCING"
+	}
+}
+
+data keycloak_openid_client_authorization_policy default {
+	realm_id           = keycloak_realm.realm.id
+	resource_server_id = keycloak_openid_client.test.resource_server_id
+	name               = "default"
+}
+
+resource keycloak_openid_client_authorization_resource test {
+	resource_server_id = keycloak_openid_client.test.resource_server_id
+	name               = "resource_name"
+	realm_id           = keycloak_realm.realm.id
+
+	uris = [
+		"/endpoint/*"
+	]
+}
+
+resource keycloak_openid_client_authorization_scope test {
+	resource_server_id = keycloak_openid_client.test.resource_server_id
+	name               = "scope_name"
+	realm_id           = keycloak_realm.realm.id
+}
+
+resource keycloak_openid_client_authorization_permission test {
+	resource_server_id = keycloak_openid_client.test.resource_server_id
+	realm_id           = keycloak_realm.realm.id
+	name               = "permission_name"
+	policies           = [data.keycloak_openid_client_authorization_policy.default.id]
+	resources          = [keycloak_openid_client_authorization_resource.test.id]
+
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this group exists in.
+- `resource_server_id` - (Required) The ID of the resource server.
+- `name` - (Required) The name of the permission.
+- `description` - (Optional) A description for the authorization permission.
+- `decision_strategy` - (Optional) The decision strategy, can be one of `UNANIMOUS`, `AFFIRMATIVE`, or `CONSENSUS`. Defaults to `UNANIMOUS`.
+- `policies` - (Optional) A list of policy IDs that must be applied to the scopes defined by this permission.
+- `resources` - (Optional) A list of resource IDs that this permission must be applied to. Conflicts with `resource_type`.
+- `resource_type` - (Optional) When specified, this permission will be evaluated for all instances of a given resource type. Conflicts with `resources`.
+- `scopes` - (Optional) A list of scope IDs that this permission must be applied to.
+- `type` - (Optional) The type of permission, can be one of `resource` or `scope`.
+
+### Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+- `id` - Permission ID representing the permission.
+
+## Import
+
+Client authorization permissions can be imported using the format: `{{realmId}}/{{resourceServerId}}/{{permissionId}}`.
+
+Example:
+
+```bash
+$ terraform import keycloak_openid_client_authorization_permission.test my-realm/3bd4a686-1062-4b59-97b8-e4e3f10b99da/63b3cde8-987d-4cd9-9306-1955579281d9
+```
diff --git a/docs/resources/openid_client_client_policy.md b/docs/resources/openid_client_client_policy.md
index 759c9c4..54ae378 100644
--- a/docs/resources/openid_client_client_policy.md
+++ b/docs/resources/openid_client_client_policy.md
@@ -10,7 +10,6 @@ This resource can be used to create client policy.
 
 In this example, we'll create a new OpenID client, then enabled permissions for the client. A client without permissions disabled cannot be assigned by a client policy. We'll use the `keycloak_openid_client_client_policy` resource to create a new client policy, which could be applied to many clients, for a realm and a resource_server_id.
 
-
 ```hcl
 resource "keycloak_realm" "realm" {
 	realm   = "my-realm"
diff --git a/docs/resources/realm.md b/docs/resources/realm.md
index 0616b58..fa457a2 100644
--- a/docs/resources/realm.md
+++ b/docs/resources/realm.md
@@ -122,6 +122,8 @@ The arguments below should be specified as [Go duration strings](https://golang.
 
 - `sso_session_idle_timeout` - (Optional) The amount of time a session can be idle before it expires.
 - `sso_session_max_lifespan` - (Optional) The maximum amount of time before a session expires regardless of activity.
+- `sso_session_idle_timeout_remember_me` - (Optional) Similar to `sso_session_idle_timeout`, but used when a user clicks "Remember Me". If not set, Keycloak will default to the value of `sso_session_idle_timeout`.
+- `sso_session_max_lifespan_remember_me` - (Optional) Similar to `sso_session_max_lifespan`, but used when a user clicks "Remember Me". If not set, Keycloak will default to the value of `sso_session_max_lifespan`.
 - `offline_session_idle_timeout` - (Optional) The amount of time an offline session can be idle before it expires.
 - `offline_session_max_lifespan` - (Optional) The maximum amount of time before an offline session expires regardless of activity.
 - `offline_session_max_lifespan_enabled` - (Optional) Enable `offline_session_max_lifespan`.
diff --git a/docs/resources/realm_user_profile.md b/docs/resources/realm_user_profile.md
index 089f05b..6cab65b 100644
--- a/docs/resources/realm_user_profile.md
+++ b/docs/resources/realm_user_profile.md
@@ -63,6 +63,17 @@ resource "keycloak_realm_user_profile" "userprofile" {
 
   attribute {
     name = "field2"
+
+    validator {
+      name   = "options"
+      config = {
+        options = jsonencode ( [ "opt1" ])
+      }
+    }
+
+    annotations = {
+      foo = jsonencode ( {"key": "val" } )
+    }
   }
 
   group {
@@ -72,6 +83,7 @@ resource "keycloak_realm_user_profile" "userprofile" {
 
     annotations = {
       foo = "bar"
+      foo2 = jsonencode ( { "key": "val" } )
     }
   }
 
@@ -97,7 +109,7 @@ resource "keycloak_realm_user_profile" "userprofile" {
 - `required_for_scopes` - (Optional) A list of scopes for which the attribute will be required.
 - `permissions` - (Optional) The [permissions](#permissions-arguments) configuration information.
 - `validator` - (Optional) A list of [validators](#validator-arguments) for the attribute.
-- `annotations` - (Optional) A map of annotations for the attribute.
+- `annotations` - (Optional) A map of annotations for the attribute. Values can be a String or a json object.
 
 #### Permissions Arguments
 
@@ -107,14 +119,14 @@ resource "keycloak_realm_user_profile" "userprofile" {
 #### Validator Arguments
 
 - `name` - (Required) The name of the validator.
-- `config` - (Optional) A map defining the configuration of the validator.
+- `config` - (Optional) A map defining the configuration of the validator. Values can be a String or a json object.
 
 ### Group Arguments
 
 - `name` - (Required) The name of the group.
 - `display_header` - (Optional) The display header of the group.
 - `display_description` - (Optional) The display description of the group.
-- `annotations` - (Optional) A map of annotations for the group.
+- `annotations` - (Optional) A map of annotations for the group. Values can be a String or a json object.
 
 ## Import
 
diff --git a/keycloak/keycloak_client.go b/keycloak/keycloak_client.go
index c37e9bc..75e1355 100644
--- a/keycloak/keycloak_client.go
+++ b/keycloak/keycloak_client.go
@@ -33,6 +33,7 @@ type KeycloakClient struct {
 	version           *version.Version
 	additionalHeaders map[string]string
 	debug             bool
+	redHatSSO         bool
 }
 
 type ClientCredentials struct {
@@ -51,7 +52,14 @@ const (
 	tokenUrl = "%s/realms/%s/protocol/openid-connect/token"
 )
 
-func NewKeycloakClient(ctx context.Context, url, basePath, clientId, clientSecret, realm, username, password string, initialLogin bool, clientTimeout int, caCert string, tlsInsecureSkipVerify bool, userAgent string, additionalHeaders map[string]string) (*KeycloakClient, error) {
+// https://access.redhat.com/articles/2342881
+var redHatSSO7VersionMap = map[int]string{
+	6: "18.0.0",
+	5: "15.0.6",
+	4: "9.0.17",
+}
+
+func NewKeycloakClient(ctx context.Context, url, basePath, clientId, clientSecret, realm, username, password string, initialLogin bool, clientTimeout int, caCert string, tlsInsecureSkipVerify bool, userAgent string, redHatSSO bool, additionalHeaders map[string]string) (*KeycloakClient, error) {
 	clientCredentials := &ClientCredentials{
 		ClientId:     clientId,
 		ClientSecret: clientSecret,
@@ -82,6 +90,7 @@ func NewKeycloakClient(ctx context.Context, url, basePath, clientId, clientSecre
 		initialLogin:      initialLogin,
 		realm:             realm,
 		userAgent:         userAgent,
+		redHatSSO:         redHatSSO,
 		additionalHeaders: additionalHeaders,
 	}
 
@@ -165,7 +174,16 @@ func (keycloakClient *KeycloakClient) login(ctx context.Context) error {
 		return err
 	}
 
-	keycloakClient.version = v
+	if keycloakClient.redHatSSO {
+		keycloakVersion, err := version.NewVersion(redHatSSO7VersionMap[v.Segments()[1]])
+		if err != nil {
+			return err
+		}
+
+		keycloakClient.version = keycloakVersion
+	} else {
+		keycloakClient.version = v
+	}
 
 	return nil
 }
diff --git a/keycloak/ldap_hardcoded_attribute_mapper.go b/keycloak/ldap_hardcoded_attribute_mapper.go
new file mode 100644
index 0000000..364e515
--- /dev/null
+++ b/keycloak/ldap_hardcoded_attribute_mapper.go
@@ -0,0 +1,76 @@
+package keycloak
+
+import (
+	"context"
+	"fmt"
+)
+
+type LdapHardcodedAttributeMapper struct {
+	Id                   string
+	Name                 string
+	RealmId              string
+	LdapUserFederationId string
+	AttributeName        string
+	AttributeValue       string
+}
+
+func convertFromLdapHardcodedAttributeMapperToComponent(ldapMapper *LdapHardcodedAttributeMapper) *component {
+	return &component{
+		Id:           ldapMapper.Id,
+		Name:         ldapMapper.Name,
+		ProviderId:   "hardcoded-ldap-attribute-mapper",
+		ProviderType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
+		ParentId:     ldapMapper.LdapUserFederationId,
+
+		Config: map[string][]string{
+			"ldap.attribute.name": {
+				ldapMapper.AttributeName,
+			},
+			"ldap.attribute.value": {
+				ldapMapper.AttributeValue,
+			},
+		},
+	}
+}
+
+func convertFromComponentToLdapHardcodedAttributeMapper(component *component, realmId string) *LdapHardcodedAttributeMapper {
+	return &LdapHardcodedAttributeMapper{
+		Id:                   component.Id,
+		Name:                 component.Name,
+		RealmId:              realmId,
+		LdapUserFederationId: component.ParentId,
+
+		AttributeName:  component.getConfig("ldap.attribute.name"),
+		AttributeValue: component.getConfig("ldap.attribute.value"),
+	}
+}
+
+func (keycloakClient *KeycloakClient) NewLdapHardcodedAttributeMapper(ctx context.Context, ldapMapper *LdapHardcodedAttributeMapper) error {
+	_, location, err := keycloakClient.post(ctx, fmt.Sprintf("/realms/%s/components", ldapMapper.RealmId), convertFromLdapHardcodedAttributeMapperToComponent(ldapMapper))
+	if err != nil {
+		return err
+	}
+
+	ldapMapper.Id = getIdFromLocationHeader(location)
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) GetLdapHardcodedAttributeMapper(ctx context.Context, realmId, id string) (*LdapHardcodedAttributeMapper, error) {
+	var component *component
+
+	err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/components/%s", realmId, id), &component, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return convertFromComponentToLdapHardcodedAttributeMapper(component, realmId), nil
+}
+
+func (keycloakClient *KeycloakClient) UpdateLdapHardcodedAttributeMapper(ctx context.Context, ldapMapper *LdapHardcodedAttributeMapper) error {
+	return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s/components/%s", ldapMapper.RealmId, ldapMapper.Id), convertFromLdapHardcodedAttributeMapperToComponent(ldapMapper))
+}
+
+func (keycloakClient *KeycloakClient) DeleteLdapHardcodedAttributeMapper(ctx context.Context, realmId, id string) error {
+	return keycloakClient.delete(ctx, fmt.Sprintf("/realms/%s/components/%s", realmId, id), nil)
+}
diff --git a/keycloak/ldap_user_federation.go b/keycloak/ldap_user_federation.go
index 23b495b..8e32a35 100644
--- a/keycloak/ldap_user_federation.go
+++ b/keycloak/ldap_user_federation.go
@@ -493,6 +493,9 @@ func (keycloakClient *KeycloakClient) GetLdapUserFederationMappers(ctx context.C
 		case "hardcoded-ldap-role-mapper":
 			mapper := convertFromComponentToLdapHardcodedRoleMapper(component, realmId)
 			ldapUserFederationMappers = append(ldapUserFederationMappers, mapper)
+		case "hardcoded-ldap-attribute-mapper":
+			mapper := convertFromComponentToLdapHardcodedAttributeMapper(component, realmId)
+			ldapUserFederationMappers = append(ldapUserFederationMappers, mapper)
 		case "msad-lds-user-account-control-mapper":
 			mapper, err := convertFromComponentToLdapMsadLdsUserAccountControlMapper(component, realmId)
 			if err != nil {
diff --git a/keycloak/openid_client_authorization_permission.go b/keycloak/openid_client_authorization_permission.go
index aadbed3..4d6e1e1 100644
--- a/keycloak/openid_client_authorization_permission.go
+++ b/keycloak/openid_client_authorization_permission.go
@@ -17,6 +17,7 @@ type OpenidClientAuthorizationPermission struct {
 	Resources        []string `json:"resources"`
 	Scopes           []string `json:"scopes"`
 	Type             string   `json:"type"`
+	ResourceType     string   `json:"resourceType,omitempty"`
 }
 
 func (keycloakClient *KeycloakClient) GetOpenidClientAuthorizationPermission(ctx context.Context, realm, resourceServerId, id string) (*OpenidClientAuthorizationPermission, error) {
diff --git a/keycloak/realm_user_profile.go b/keycloak/realm_user_profile.go
index 0143a40..ce18ebf 100644
--- a/keycloak/realm_user_profile.go
+++ b/keycloak/realm_user_profile.go
@@ -23,7 +23,7 @@ type RealmUserProfileSelector struct {
 type RealmUserProfileValidationConfig map[string]interface{}
 
 type RealmUserProfileAttribute struct {
-	Annotations map[string]string                           `json:"annotations,omitempty"`
+	Annotations map[string]interface{}                      `json:"annotations,omitempty"`
 	DisplayName string                                      `json:"displayName,omitempty"`
 	Group       string                                      `json:"group,omitempty"`
 	Name        string                                      `json:"name"`
@@ -34,10 +34,10 @@ type RealmUserProfileAttribute struct {
 }
 
 type RealmUserProfileGroup struct {
-	Annotations        map[string]string `json:"annotations,omitempty"`
-	DisplayDescription string            `json:"displayDescription,omitempty"`
-	DisplayHeader      string            `json:"displayHeader,omitempty"`
-	Name               string            `json:"name"`
+	Annotations        map[string]interface{} `json:"annotations,omitempty"`
+	DisplayDescription string                 `json:"displayDescription,omitempty"`
+	DisplayHeader      string                 `json:"displayHeader,omitempty"`
+	Name               string                 `json:"name"`
 }
 
 type RealmUserProfile struct {
@@ -65,5 +65,50 @@ func (keycloakClient *KeycloakClient) GetRealmUserProfile(ctx context.Context, r
 		return nil, err
 	}
 
+	for _, attr := range realmUserProfile.Attributes {
+		if attr.Validations != nil {
+			for name, config := range attr.Validations {
+
+				c := make(map[string]interface{})
+				for k, v := range config {
+					if _, ok := v.([]interface{}); ok {
+						tmp, _ := json.Marshal(v)
+						c[k] = string(tmp)
+					} else {
+						c[k] = v
+					}
+				}
+
+				attr.Validations[name] = c
+			}
+		}
+		if attr.Annotations != nil {
+			for k, v := range attr.Annotations {
+
+				if _, ok := v.(map[string]interface{}); ok {
+					tmp, _ := json.Marshal(v)
+					attr.Annotations[k] = string(tmp)
+				} else {
+					attr.Annotations[k] = v
+				}
+
+			}
+		}
+	}
+
+	for _, attr := range realmUserProfile.Groups {
+		if attr.Annotations != nil {
+			for k, v := range attr.Annotations {
+
+				if _, ok := v.(map[string]interface{}); ok {
+					tmp, _ := json.Marshal(v)
+					attr.Annotations[k] = string(tmp)
+				} else {
+					attr.Annotations[k] = v
+				}
+
+			}
+		}
+	}
 	return &realmUserProfile, nil
 }
diff --git a/provider/provider.go b/provider/provider.go
index e8ce151..c0fc12d 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -53,6 +53,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
 			"keycloak_ldap_group_mapper":                                 resourceKeycloakLdapGroupMapper(),
 			"keycloak_ldap_role_mapper":                                  resourceKeycloakLdapRoleMapper(),
 			"keycloak_ldap_hardcoded_role_mapper":                        resourceKeycloakLdapHardcodedRoleMapper(),
+			"keycloak_ldap_hardcoded_attribute_mapper":                   resourceKeycloakLdapHardcodedAttributeMapper(),
 			"keycloak_ldap_hardcoded_group_mapper":                       resourceKeycloakLdapHardcodedGroupMapper(),
 			"keycloak_ldap_msad_user_account_control_mapper":             resourceKeycloakLdapMsadUserAccountControlMapper(),
 			"keycloak_ldap_msad_lds_user_account_control_mapper":         resourceKeycloakLdapMsadLdsUserAccountControlMapper(),
@@ -169,6 +170,12 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
 				Description: "Allows ignoring insecure certificates when set to true. Defaults to false. Disabling security check is dangerous and should be avoided.",
 				Default:     false,
 			},
+			"red_hat_sso": {
+				Optional:    true,
+				Type:        schema.TypeBool,
+				Description: "When true, the provider will treat the Keycloak instance as a Red Hat SSO server, specifically when parsing the version returned from the /serverinfo API endpoint.",
+				Default:     false,
+			},
 			"base_path": {
 				Optional:    true,
 				Type:        schema.TypeString,
@@ -200,6 +207,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
 		clientTimeout := data.Get("client_timeout").(int)
 		tlsInsecureSkipVerify := data.Get("tls_insecure_skip_verify").(bool)
 		rootCaCertificate := data.Get("root_ca_certificate").(string)
+		redHatSSO := data.Get("red_hat_sso").(bool)
 		additionalHeaders := make(map[string]string)
 		for k, v := range data.Get("additional_headers").(map[string]interface{}) {
 			additionalHeaders[k] = v.(string)
@@ -209,7 +217,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
 
 		userAgent := fmt.Sprintf("HashiCorp Terraform/%s (+https://www.terraform.io) Terraform Plugin SDK/%s", provider.TerraformVersion, meta.SDKVersionString())
 
-		keycloakClient, err := keycloak.NewKeycloakClient(ctx, url, basePath, clientId, clientSecret, realm, username, password, initialLogin, clientTimeout, rootCaCertificate, tlsInsecureSkipVerify, userAgent, additionalHeaders)
+		keycloakClient, err := keycloak.NewKeycloakClient(ctx, url, basePath, clientId, clientSecret, realm, username, password, initialLogin, clientTimeout, rootCaCertificate, tlsInsecureSkipVerify, userAgent, redHatSSO, additionalHeaders)
 		if err != nil {
 			diags = append(diags, diag.Diagnostic{
 				Severity: diag.Error,
diff --git a/provider/resource_keycloak_ldap_hardcoded_attribute_mapper.go b/provider/resource_keycloak_ldap_hardcoded_attribute_mapper.go
new file mode 100644
index 0000000..2f495c2
--- /dev/null
+++ b/provider/resource_keycloak_ldap_hardcoded_attribute_mapper.go
@@ -0,0 +1,130 @@
+package provider
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func resourceKeycloakLdapHardcodedAttributeMapper() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceKeycloakLdapHardcodedAttributeMapperCreate,
+		ReadContext:   resourceKeycloakLdapHardcodedAttributeMapperRead,
+		UpdateContext: resourceKeycloakLdapHardcodedAttributeMapperUpdate,
+		DeleteContext: resourceKeycloakLdapHardcodedAttributeMapperDelete,
+		// This resource can be imported using {{realm}}/{{provider_id}}/{{mapper_id}}. The Provider and Mapper IDs are displayed in the GUI
+		Importer: &schema.ResourceImporter{
+			StateContext: resourceKeycloakLdapGenericMapperImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"name": {
+				Type:        schema.TypeString,
+				Required:    true,
+				Description: "Display name of the mapper when displayed in the console.",
+			},
+			"realm_id": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: "The realm in which the ldap user federation provider exists.",
+			},
+			"ldap_user_federation_id": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: "The ldap user federation provider to attach this mapper to.",
+			},
+			"attribute_name": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: "Name of the LDAP attribute",
+			},
+			"attribute_value": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: "Value of the LDAP attribute. You can hardcode any value like 'foo'",
+			},
+		},
+	}
+}
+
+func getLdapHardcodedAttributeMapperFromData(data *schema.ResourceData) *keycloak.LdapHardcodedAttributeMapper {
+	return &keycloak.LdapHardcodedAttributeMapper{
+		Id:                   data.Id(),
+		Name:                 data.Get("name").(string),
+		RealmId:              data.Get("realm_id").(string),
+		LdapUserFederationId: data.Get("ldap_user_federation_id").(string),
+		AttributeName:        data.Get("attribute_name").(string),
+		AttributeValue:       data.Get("attribute_value").(string),
+	}
+}
+
+func setLdapHardcodedAttributeMapperData(data *schema.ResourceData, ldapMapper *keycloak.LdapHardcodedAttributeMapper) {
+	data.SetId(ldapMapper.Id)
+	data.Set("name", ldapMapper.Name)
+	data.Set("realm_id", ldapMapper.RealmId)
+	data.Set("ldap_user_federation_id", ldapMapper.LdapUserFederationId)
+	data.Set("attribute_name", ldapMapper.AttributeName)
+	data.Set("attribute_value", ldapMapper.AttributeValue)
+}
+
+func resourceKeycloakLdapHardcodedAttributeMapperCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	ldapMapper := getLdapHardcodedAttributeMapperFromData(data)
+
+	err := keycloakClient.NewLdapHardcodedAttributeMapper(ctx, ldapMapper)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	setLdapHardcodedAttributeMapperData(data, ldapMapper)
+
+	return resourceKeycloakLdapHardcodedAttributeMapperRead(ctx, data, meta)
+}
+
+func resourceKeycloakLdapHardcodedAttributeMapperRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	ldapMapper, err := keycloakClient.GetLdapHardcodedAttributeMapper(ctx, realmId, id)
+	if err != nil {
+		return handleNotFoundError(ctx, err, data)
+	}
+
+	setLdapHardcodedAttributeMapperData(data, ldapMapper)
+
+	return diag.FromErr(nil)
+}
+
+func resourceKeycloakLdapHardcodedAttributeMapperUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	ldapMapper := getLdapHardcodedAttributeMapperFromData(data)
+
+	err := keycloakClient.UpdateLdapHardcodedAttributeMapper(ctx, ldapMapper)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	setLdapHardcodedAttributeMapperData(data, ldapMapper)
+
+	return diag.FromErr(nil)
+}
+
+func resourceKeycloakLdapHardcodedAttributeMapperDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	err := keycloakClient.DeleteLdapHardcodedAttributeMapper(ctx, realmId, id)
+
+	return diag.FromErr(err)
+}
diff --git a/provider/resource_keycloak_openid_client_authorization_permission.go b/provider/resource_keycloak_openid_client_authorization_permission.go
index 34f5a2f..0a58d5c 100644
--- a/provider/resource_keycloak_openid_client_authorization_permission.go
+++ b/provider/resource_keycloak_openid_client_authorization_permission.go
@@ -3,9 +3,10 @@ package provider
 import (
 	"context"
 	"fmt"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"strings"
 
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+
 	"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"
@@ -55,9 +56,15 @@ func resourceKeycloakOpenidClientAuthorizationPermission() *schema.Resource {
 				Optional: true,
 			},
 			"resources": {
-				Type:     schema.TypeSet,
-				Elem:     &schema.Schema{Type: schema.TypeString},
-				Optional: true,
+				Type:          schema.TypeSet,
+				Elem:          &schema.Schema{Type: schema.TypeString},
+				Optional:      true,
+				ConflictsWith: []string{"resource_type"},
+			},
+			"resource_type": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ConflictsWith: []string{"resources"},
 			},
 			"scopes": {
 				Type:     schema.TypeSet,
@@ -105,6 +112,7 @@ func getOpenidClientAuthorizationPermissionFromData(data *schema.ResourceData) *
 		Policies:         policies,
 		Scopes:           scopes,
 		Resources:        resources,
+		ResourceType:     data.Get("resource_type").(string),
 	}
 	return &permission
 }
@@ -120,6 +128,7 @@ func setOpenidClientAuthorizationPermissionData(data *schema.ResourceData, permi
 	data.Set("policies", permission.Policies)
 	data.Set("scopes", permission.Scopes)
 	data.Set("resources", permission.Resources)
+	data.Set("resource_type", permission.ResourceType)
 }
 
 func resourceKeycloakOpenidClientAuthorizationPermissionCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
diff --git a/provider/resource_keycloak_realm_user_profile.go b/provider/resource_keycloak_realm_user_profile.go
index 9d1dc1a..b8ab597 100644
--- a/provider/resource_keycloak_realm_user_profile.go
+++ b/provider/resource_keycloak_realm_user_profile.go
@@ -2,6 +2,9 @@ package provider
 
 import (
 	"context"
+	"encoding/json"
+	"strings"
+
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
@@ -181,7 +184,14 @@ func getRealmUserProfileAttributeFromData(m map[string]interface{}) *keycloak.Re
 			config := make(map[string]interface{})
 			if v, ok := validationConfig["config"]; ok {
 				for key, value := range v.(map[string]interface{}) {
-					config[key] = value
+					if strings.HasPrefix(value.(string), "[") {
+						t := make([]interface{}, 0)
+						json.Unmarshal([]byte(value.(string)), &t)
+
+						config[key] = t
+					} else {
+						config[key] = value
+					}
 				}
 			}
 
@@ -205,9 +215,17 @@ func getRealmUserProfileAttributeFromData(m map[string]interface{}) *keycloak.Re
 	}
 
 	if v, ok := m["annotations"]; ok {
-		annotations := make(map[string]string)
+		annotations := make(map[string]interface{})
+
 		for key, value := range v.(map[string]interface{}) {
-			annotations[key] = value.(string)
+
+			if strings.HasPrefix(value.(string), "{") {
+				var t interface{}
+				json.Unmarshal([]byte(value.(string)), &t)
+				annotations[key] = t
+			} else {
+				annotations[key] = value
+			}
 		}
 		attribute.Annotations = annotations
 	}
@@ -237,10 +255,19 @@ func getRealmUserProfileGroupFromData(m map[string]interface{}) *keycloak.RealmU
 	}
 
 	if v, ok := m["annotations"]; ok {
-		annotations := make(map[string]string)
+		annotations := make(map[string]interface{})
+
 		for key, value := range v.(map[string]interface{}) {
-			annotations[key] = value.(string)
+			if strings.HasPrefix(value.(string), "{") {
+				var t interface{}
+				json.Unmarshal([]byte(value.(string)), &t)
+
+				annotations[key] = t
+			} else {
+				annotations[key] = value
+			}
 		}
+
 		group.Annotations = annotations
 	}
 
@@ -302,7 +329,18 @@ func getRealmUserProfileAttributeData(attr *keycloak.RealmUserProfileAttribute)
 			validator := make(map[string]interface{})
 
 			validator["name"] = name
-			validator["config"] = config
+
+			c := make(map[string]interface{})
+			for k, v := range config {
+				if _, ok := v.([]interface{}); ok {
+					t, _ := json.Marshal(v)
+					c[k] = string(t)
+				} else {
+					c[k] = v
+				}
+			}
+
+			validator["config"] = c
 
 			validations = append(validations, validator)
 		}
@@ -310,7 +348,18 @@ func getRealmUserProfileAttributeData(attr *keycloak.RealmUserProfileAttribute)
 	}
 
 	if attr.Annotations != nil {
-		attributeData["annotations"] = attr.Annotations
+		annotations := make(map[string]interface{})
+
+		for k, v := range attr.Annotations {
+			if _, ok := v.(map[string]interface{}); ok {
+				t, _ := json.Marshal(v)
+				annotations[k] = string(t)
+			} else {
+				annotations[k] = v
+			}
+		}
+
+		attributeData["annotations"] = annotations
 	}
 
 	return attributeData
@@ -322,7 +371,19 @@ func getRealmUserProfileGroupData(group *keycloak.RealmUserProfileGroup) map[str
 	groupData["name"] = group.Name
 	groupData["display_header"] = group.DisplayHeader
 	groupData["display_description"] = group.DisplayDescription
-	groupData["annotations"] = group.Annotations
+
+	annotations := make(map[string]interface{})
+
+	for k, v := range group.Annotations {
+		if _, ok := v.(map[string]interface{}); ok {
+			t, _ := json.Marshal(v)
+			annotations[k] = string(t)
+		} else {
+			annotations[k] = v
+		}
+	}
+
+	groupData["annotations"] = annotations
 
 	return groupData
 }


Test patch:
diff --git a/keycloak/keycloak_client_test.go b/keycloak/keycloak_client_test.go
index 4da400f..98fa214 100644
--- a/keycloak/keycloak_client_test.go
+++ b/keycloak/keycloak_client_test.go
@@ -48,7 +48,7 @@ func TestAccKeycloakApiClientRefresh(t *testing.T) {
 		t.Fatal("KEYCLOAK_CLIENT_TIMEOUT must be an integer")
 	}
 
-	keycloakClient, err := NewKeycloakClient(ctx, os.Getenv("KEYCLOAK_URL"), "/auth", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), os.Getenv("KEYCLOAK_USER"), os.Getenv("KEYCLOAK_PASSWORD"), true, clientTimeout, "", false, "", map[string]string{
+	keycloakClient, err := NewKeycloakClient(ctx, os.Getenv("KEYCLOAK_URL"), "/auth", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), os.Getenv("KEYCLOAK_USER"), os.Getenv("KEYCLOAK_PASSWORD"), true, clientTimeout, "", false, "", false, map[string]string{
 		"foo": "bar",
 	})
 	if err != nil {
diff --git a/provider/provider_test.go b/provider/provider_test.go
index ef84055..af74ba2 100644
--- a/provider/provider_test.go
+++ b/provider/provider_test.go
@@ -29,7 +29,7 @@ var requiredEnvironmentVariables = []string{
 func init() {
 	testCtx = context.Background()
 	userAgent := fmt.Sprintf("HashiCorp Terraform/%s (+https://www.terraform.io) Terraform Plugin SDK/%s", schema.Provider{}.TerraformVersion, meta.SDKVersionString())
-	keycloakClient, _ = keycloak.NewKeycloakClient(testCtx, os.Getenv("KEYCLOAK_URL"), "/auth", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), "", "", true, 5, "", false, userAgent, map[string]string{
+	keycloakClient, _ = keycloak.NewKeycloakClient(testCtx, os.Getenv("KEYCLOAK_URL"), "/auth", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), "", "", true, 5, "", false, userAgent, false, map[string]string{
 		"foo": "bar",
 	})
 	testAccProvider = KeycloakProvider(keycloakClient)
diff --git a/provider/resource_keycloak_ldap_hardcoded_attribute_mapper_test.go b/provider/resource_keycloak_ldap_hardcoded_attribute_mapper_test.go
new file mode 100644
index 0000000..f5aefbf
--- /dev/null
+++ b/provider/resource_keycloak_ldap_hardcoded_attribute_mapper_test.go
@@ -0,0 +1,165 @@
+package provider
+
+import (
+	"context"
+	"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 TestAccKeycloakLdapHardcodedAttributeMapper_basic(t *testing.T) {
+	t.Parallel()
+	attributeName := acctest.RandomWithPrefix("tf-acc")
+	attributeValue := acctest.RandomWithPrefix("tf-acc")
+	attributeMapperName := acctest.RandomWithPrefix("tf-acc")
+
+	resource.Test(t, resource.TestCase{
+		ProviderFactories: testAccProviderFactories,
+		PreCheck:          func() { testAccPreCheck(t) },
+		CheckDestroy:      testAccCheckKeycloakLdapHardcodedAttributeMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakLdapHardcodedAttributeMapper(attributeMapperName, attributeName, attributeValue),
+				Check:  testAccCheckKeycloakLdapHardcodedAttributeMapperExists("keycloak_ldap_hardcoded_attribute_mapper.hardcoded_attribute_mapper"),
+			},
+			{
+				ResourceName:      "keycloak_ldap_hardcoded_attribute_mapper.hardcoded_attribute_mapper",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getLdapGenericMapperImportId("keycloak_ldap_hardcoded_attribute_mapper.hardcoded_attribute_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapHardcodedAttributeMapper_createAfterManualDestroy(t *testing.T) {
+	t.Parallel()
+	var mapper = &keycloak.LdapHardcodedAttributeMapper{}
+
+	attributeName := acctest.RandomWithPrefix("tf-acc")
+	attributeValue := acctest.RandomWithPrefix("tf-acc")
+	attributeMapperName := acctest.RandomWithPrefix("tf-acc")
+
+	resource.Test(t, resource.TestCase{
+		ProviderFactories: testAccProviderFactories,
+		PreCheck:          func() { testAccPreCheck(t) },
+		CheckDestroy:      testAccCheckKeycloakLdapHardcodedAttributeMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakLdapHardcodedAttributeMapper(attributeMapperName, attributeName, attributeValue),
+				Check:  testAccCheckKeycloakLdapHardcodedAttributeMapperFetch("keycloak_ldap_hardcoded_attribute_mapper.hardcoded_attribute_mapper", mapper),
+			},
+			{
+				PreConfig: func() {
+					err := keycloakClient.DeleteLdapHardcodedAttributeMapper(context.Background(), mapper.RealmId, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakLdapHardcodedAttributeMapper(attributeMapperName, attributeName, attributeValue),
+				Check:  testAccCheckKeycloakLdapHardcodedAttributeMapperExists("keycloak_ldap_hardcoded_attribute_mapper.hardcoded_attribute_mapper"),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakLdapHardcodedAttributeMapperExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		_, err := getLdapHardcodedAttributeMapperFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakLdapHardcodedAttributeMapperFetch(resourceName string, mapper *keycloak.LdapHardcodedAttributeMapper) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedMapper, err := getLdapHardcodedAttributeMapperFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		mapper.Id = fetchedMapper.Id
+		mapper.RealmId = fetchedMapper.RealmId
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakLdapHardcodedAttributeMapperDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_ldap_hardcoded_attribute_mapper" {
+				continue
+			}
+
+			id := rs.Primary.ID
+			realm := rs.Primary.Attributes["realm_id"]
+
+			ldapMapper, _ := keycloakClient.GetLdapHardcodedAttributeMapper(context.Background(), realm, id)
+			if ldapMapper != nil {
+				return fmt.Errorf("ldap hardcoded attribute mapper with id %s still exists", id)
+			}
+		}
+
+		return nil
+	}
+}
+
+func getLdapHardcodedAttributeMapperFromState(s *terraform.State, resourceName string) (*keycloak.LdapHardcodedAttributeMapper, error) {
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	id := rs.Primary.ID
+	realm := rs.Primary.Attributes["realm_id"]
+
+	ldapMapper, err := keycloakClient.GetLdapHardcodedAttributeMapper(context.Background(), realm, id)
+	if err != nil {
+		return nil, fmt.Errorf("error getting ldap attribute mapper with id %s: %s", id, err)
+	}
+
+	return ldapMapper, nil
+}
+
+func testKeycloakLdapHardcodedAttributeMapper(attributeMapperName, attributeName, attributeValue string) string {
+	return fmt.Sprintf(`
+data "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_ldap_user_federation" "openldap" {
+	name                    = "openldap"
+	realm_id                = data.keycloak_realm.realm.id
+
+	enabled                 = true
+
+	username_ldap_attribute = "cn"
+	rdn_ldap_attribute      = "cn"
+	uuid_ldap_attribute     = "entryDN"
+	user_object_classes     = [
+		"simpleSecurityObject",
+		"organizationalRole"
+	]
+	connection_url          = "ldap://openldap"
+	users_dn                = "dc=example,dc=org"
+	bind_dn                 = "cn=admin,dc=example,dc=org"
+	bind_credential         = "admin"
+}
+
+resource "keycloak_ldap_hardcoded_attribute_mapper" "hardcoded_attribute_mapper" {
+	name                        = "%s"
+	realm_id                    = data.keycloak_realm.realm.id
+	ldap_user_federation_id     = keycloak_ldap_user_federation.openldap.id
+	attribute_name              = "%s"
+	attribute_value             = "%s"
+}
+	`, testAccRealmUserFederation.Realm, attributeMapperName, attributeName, attributeValue)
+}
diff --git a/provider/resource_keycloak_ldap_hardcoded_role_mapper_test.go b/provider/resource_keycloak_ldap_hardcoded_role_mapper_test.go
index 9601197..91ae9b8 100644
--- a/provider/resource_keycloak_ldap_hardcoded_role_mapper_test.go
+++ b/provider/resource_keycloak_ldap_hardcoded_role_mapper_test.go
@@ -120,7 +120,7 @@ func getLdapHardcodedRoleMapperFromState(s *terraform.State, resourceName string
 
 	ldapMapper, err := keycloakClient.GetLdapHardcodedRoleMapper(testCtx, realm, id)
 	if err != nil {
-		return nil, fmt.Errorf("error getting ldap group mapper with id %s: %s", id, err)
+		return nil, fmt.Errorf("error getting ldap role mapper with id %s: %s", id, err)
 	}
 
 	return ldapMapper, nil
diff --git a/provider/resource_keycloak_openid_client_authorization_permission_test.go b/provider/resource_keycloak_openid_client_authorization_permission_test.go
index b78d6db..7bb0a2d 100644
--- a/provider/resource_keycloak_openid_client_authorization_permission_test.go
+++ b/provider/resource_keycloak_openid_client_authorization_permission_test.go
@@ -30,6 +30,27 @@ func TestAccKeycloakOpenidClientAuthorizationPermission_basic(t *testing.T) {
 	})
 }
 
+func TestAccKeycloakOpenidClientAuthorizationPermission_resourceType(t *testing.T) {
+	t.Parallel()
+	clientId := acctest.RandomWithPrefix("tf-acc")
+	resourceName := acctest.RandomWithPrefix("tf-acc")
+	resourceType := acctest.RandomWithPrefix("tf-acc")
+	permissionName := acctest.RandomWithPrefix("tf-acc")
+	scopeName := acctest.RandomWithPrefix("tf-acc")
+
+	resource.Test(t, resource.TestCase{
+		ProviderFactories: testAccProviderFactories,
+		PreCheck:          func() { testAccPreCheck(t) },
+		CheckDestroy:      testAccCheckKeycloakOpenidClientAuthorizationPermissionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenidClientAuthorizationPermission_resourceType(clientId, resourceName, resourceType, permissionName, scopeName),
+				Check:  testAccCheckKeycloakOpenidClientAuthorizationPermissionExists("keycloak_openid_client_authorization_permission.test"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakOpenidClientAuthorizationPermission_createAfterManualDestroy(t *testing.T) {
 	t.Parallel()
 	var authorizationPermission = &keycloak.OpenidClientAuthorizationPermission{}
@@ -178,25 +199,25 @@ resource keycloak_openid_client test {
 }
 
 data keycloak_openid_client_authorization_policy default {
-  realm_id           = data.keycloak_realm.realm.id
-  resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
-  name               = "default"
+	realm_id           = data.keycloak_realm.realm.id
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "default"
 }
 
 resource keycloak_openid_client_authorization_resource test {
-  resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
-  name               = "%s"
-  realm_id           = data.keycloak_realm.realm.id
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "%s"
+	realm_id           = data.keycloak_realm.realm.id
 
-  uris = [
-    "/endpoint/*"
-  ]
+	uris = [
+		"/endpoint/*"
+	]
 }
 
 resource keycloak_openid_client_authorization_scope test {
-  resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
-  name               = "%s"
-  realm_id           = data.keycloak_realm.realm.id
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "%s"
+	realm_id           = data.keycloak_realm.realm.id
 }
 
 resource keycloak_openid_client_authorization_permission test {
@@ -210,6 +231,57 @@ resource keycloak_openid_client_authorization_permission test {
 	`, testAccRealm.Realm, clientId, resourceName, scopeName, permissionName)
 }
 
+func testKeycloakOpenidClientAuthorizationPermission_resourceType(clientId, resourceName, resourceType, permissionName, scopeName string) string {
+	return fmt.Sprintf(`
+data "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource keycloak_openid_client test {
+	client_id                = "%s"
+	realm_id                 = data.keycloak_realm.realm.id
+	access_type              = "CONFIDENTIAL"
+	service_accounts_enabled = true
+	authorization {
+		policy_enforcement_mode = "ENFORCING"
+	}
+}
+
+data keycloak_openid_client_authorization_policy default {
+	realm_id           = data.keycloak_realm.realm.id
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "default"
+}
+
+resource keycloak_openid_client_authorization_resource test {
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "%s"
+	realm_id           = data.keycloak_realm.realm.id
+
+	type = "%s"
+
+	uris = [
+		"/endpoint/*"
+	]
+}
+
+resource keycloak_openid_client_authorization_scope test {
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "%s"
+	realm_id           = data.keycloak_realm.realm.id
+}
+
+resource keycloak_openid_client_authorization_permission test {
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	realm_id           = data.keycloak_realm.realm.id
+	name               = "%s"
+	policies           = ["${data.keycloak_openid_client_authorization_policy.default.id}"]
+	resource_type      = "%s"
+
+}
+	`, testAccRealm.Realm, clientId, resourceName, resourceType, scopeName, permissionName, resourceType)
+}
+
 func testKeycloakOpenidClientAuthorizationPermission_basicFromInterface(clientId string, authorizationPermission *keycloak.OpenidClientAuthorizationPermission, resourceName, scopeName string) string {
 	return fmt.Sprintf(`
 data "keycloak_realm" "realm" {
@@ -227,25 +299,25 @@ resource keycloak_openid_client test {
 }
 
 data keycloak_openid_client_authorization_policy default {
-  realm_id           = data.keycloak_realm.realm.id
-  resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
-  name               = "default"
+	realm_id           = data.keycloak_realm.realm.id
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "default"
 }
 
 resource keycloak_openid_client_authorization_resource resource {
-  resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
-  name               = "%s"
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "%s"
 	realm_id           = data.keycloak_realm.realm.id
 
-  uris = [
-    "/endpoint/*"
-  ]
+	uris = [
+		"/endpoint/*"
+	]
 }
 
 resource keycloak_openid_client_authorization_scope test {
-  resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
-  name               = "%s"
-  realm_id           = data.keycloak_realm.realm.id
+	resource_server_id = "${keycloak_openid_client.test.resource_server_id}"
+	name               = "%s"
+	realm_id           = data.keycloak_realm.realm.id
 }
 
 resource keycloak_openid_client_authorization_permission test {
@@ -253,8 +325,8 @@ resource keycloak_openid_client_authorization_permission test {
 	realm_id           = data.keycloak_realm.realm.id
 	name               = "%s"
 	policies           = ["${data.keycloak_openid_client_authorization_policy.default.id}"]
-   resources          = ["${keycloak_openid_client_authorization_resource.resource.id}"]
-	 description        = "%s"
+	resources          = ["${keycloak_openid_client_authorization_resource.resource.id}"]
+	description        = "%s"
 	scopes = ["${keycloak_openid_client_authorization_scope.test.id}"]
 }
 	`, testAccRealm.Realm, clientId, resourceName, scopeName, authorizationPermission.Name, authorizationPermission.Description)
diff --git a/provider/resource_keycloak_realm_user_profile_test.go b/provider/resource_keycloak_realm_user_profile_test.go
index 3ba652e..116e39d 100644
--- a/provider/resource_keycloak_realm_user_profile_test.go
+++ b/provider/resource_keycloak_realm_user_profile_test.go
@@ -75,13 +75,24 @@ func TestAccKeycloakRealmUserProfile_basicFull(t *testing.T) {
 				},
 				Validations: map[string]keycloak.RealmUserProfileValidationConfig{
 					"person-name-prohibited-characters": map[string]interface{}{},
-					"pattern":                           map[string]interface{}{"pattern": "^[a-z]+$", "error_message": "Error!"},
+					"pattern":                           map[string]interface{}{"pattern": "\"^[a-z]+$\"", "error_message": "\"Error!\""},
+				},
+				Annotations: map[string]interface{}{
+					"foo":               "\"bar\"",
+					"inputOptionLabels": "{\"a\":\"b\"}",
 				},
-				Annotations: map[string]string{"foo": "bar"},
 			},
 		},
 		Groups: []*keycloak.RealmUserProfileGroup{
-			{Name: "group", DisplayDescription: "Description", DisplayHeader: "Header", Annotations: map[string]string{"foo": "bar"}},
+			{
+				Name:               "group",
+				DisplayDescription: "Description",
+				DisplayHeader:      "Header",
+				Annotations: map[string]interface{}{
+					"foo":  "\"bar\"",
+					"test": "{\"a2\":\"b2\"}",
+				},
+			},
 		},
 	}
 
@@ -165,7 +176,8 @@ func TestAccKeycloakRealmUserProfile_attributeValidator(t *testing.T) {
 			{
 				Name: "attribute",
 				Validations: map[string]keycloak.RealmUserProfileValidationConfig{
-					"length": map[string]interface{}{"min": "5", "max": "10"},
+					"length":  map[string]interface{}{"min": "5", "max": "10"},
+					"options": map[string]interface{}{"options": "[\"cgu\"]"},
 				},
 			},
 		},
@@ -405,7 +417,7 @@ resource "keycloak_realm_user_profile" "realm_user_profile" {
             {{- if $config }}
             config = {
                 {{- range $key, $value := $config }}
-                {{ $key }} = "{{ $value }}"
+                {{ $key }} = jsonencode ( {{ $value }} )
                 {{- end }}
             }
             {{- end }}
@@ -416,7 +428,7 @@ resource "keycloak_realm_user_profile" "realm_user_profile" {
 		{{- if $attribute.Annotations }}
         annotations = {
             {{- range $key, $value := $attribute.Annotations }}
-            {{ $key }} = "{{ $value }}"
+            {{ $key }} = jsonencode ( {{ $value }} )
             {{- end }}
         }
 		{{- end }}
@@ -438,7 +450,7 @@ resource "keycloak_realm_user_profile" "realm_user_profile" {
 		{{- if $group.Annotations }}
         annotations = {
             {{- range $key, $value := $group.Annotations }}
-            {{ $key }} = "{{ $value }}"
+            {{ $key }} = jsonencode ( {{ $value }} )
             {{- end }}
         }
 		{{- end }}