Repository: mrparkers/terraform-provider-keycloak
Pull request #158 (2019-10-01 18:29:30)

Fix incorrect default for reset_credentials_flow


Patch:
diff --git a/docs/resources/keycloak_openid_user_realm_role_protocol_mapper.md b/docs/resources/keycloak_openid_user_realm_role_protocol_mapper.md
new file mode 100644
index 0000000..e1df531
--- /dev/null
+++ b/docs/resources/keycloak_openid_user_realm_role_protocol_mapper.md
@@ -0,0 +1,90 @@
+# keycloak_openid_user_realm_role_protocol_mapper
+
+Allows for creating and managing user realm role protocol mappers within
+Keycloak.
+
+User realm role protocol mappers allow you to define a claim containing the list of the realm roles.
+Protocol mappers can be defined for a single client, or they can
+be defined for a client scope which can be shared between multiple different
+clients.
+
+### Example Usage (Client)
+
+```hcl
+resource "keycloak_realm" "realm" {
+    realm   = "my-realm"
+    enabled = true
+}
+
+resource "keycloak_openid_client" "openid_client" {
+    realm_id            = "${keycloak_realm.realm.id}"
+    client_id           = "test-client"
+
+    name                = "test client"
+    enabled             = true
+
+    access_type         = "CONFIDENTIAL"
+    valid_redirect_uris = [
+        "http://localhost:8080/openid-callback"
+    ]
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper" {
+    realm_id    = "${keycloak_realm.realm.id}"
+    client_id   = "${keycloak_openid_client.openid_client.id}"
+    name        = "user-realm-role-mapper"
+
+    claim_name  = "foo"
+}
+```
+
+### Example Usage (Client Scope)
+
+```hcl
+resource "keycloak_realm" "realm" {
+    realm   = "my-realm"
+    enabled = true
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+    realm_id = "${keycloak_realm.realm.id}"
+    name     = "test-client-scope"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper" {
+    realm_id        = "${keycloak_realm.realm.id}"
+    client_scope_id = "${keycloak_openid_client_scope.client_scope.id}"
+    name            = "user-realm-role-mapper"
+
+    claim_name      = "foo"
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this protocol mapper exists within.
+- `client_id` - (Required if `client_scope_id` is not specified) The client this protocol mapper is attached to.
+- `client_scope_id` - (Required if `client_id` is not specified) The client scope this protocol mapper is attached to.
+- `name` - (Required) The display name of this protocol mapper in the GUI.
+- `claim_name` - (Required) The name of the claim to insert into a token.
+- `claim_value_type` - (Optional) The claim type used when serializing JSON tokens. Can be one of `String`, `long`, `int`, or `boolean`. Defaults to `String`.
+- `multivalued` - (Optional) Indicates if attribute supports multiple values. If true, then the list of all values of this attribute will be set as claim. If false, then just first value will be set as claim. Defaults to `true`.
+- `realm_role_prefix` - (Optional) A prefix for each Realm Role.
+- `add_to_id_token` - (Optional) Indicates if the property should be added as a claim to the id token. Defaults to `true`.
+- `add_to_access_token` - (Optional) Indicates if the property should be added as a claim to the access token. Defaults to `true`.
+- `add_to_userinfo` - (Optional) Indicates if the property should be added as a claim to the UserInfo response body. Defaults to `true`.
+
+### Import
+
+Protocol mappers can be imported using one of the following formats:
+- Client: `{{realm_id}}/client/{{client_keycloak_id}}/{{protocol_mapper_id}}`
+- Client Scope: `{{realm_id}}/client-scope/{{client_scope_keycloak_id}}/{{protocol_mapper_id}}`
+
+Example:
+
+```bash
+$ terraform import keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper my-realm/client/a7202154-8793-4656-b655-1dd18c181e14/71602afa-f7d1-4788-8c49-ef8fd00af0f4
+$ terraform import keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper my-realm/client-scope/b799ea7e-73ee-4a73-990a-1eafebe8e20a/71602afa-f7d1-4788-8c49-ef8fd00af0f4
+```
diff --git a/example/main.tf b/example/main.tf
index e24bc6c..e8df7ef 100644
--- a/example/main.tf
+++ b/example/main.tf
@@ -355,6 +355,22 @@ resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_clie
   claim_value = "bar"
 }
 
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_client" {
+  name      = "tf-test-open-id-user-realm-role-claim-protocol-mapper-client"
+  realm_id  = "${keycloak_realm.test.id}"
+  client_id = "${keycloak_openid_client.test_client.id}"
+
+  claim_name  = "foo"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_client_scope" {
+  name            = "tf-test-open-id-user-realm-role-protocol-mapper-client-scope"
+  realm_id        = "${keycloak_realm.test.id}"
+  client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}"
+
+  claim_name  = "foo"
+}
+
 resource "keycloak_openid_client" "bearer_only_client" {
   client_id   = "test-bearer-only-client"
   name        = "test-bearer-only-client"
diff --git a/keycloak/openid_user_realm_role_protocol_mapper.go b/keycloak/openid_user_realm_role_protocol_mapper.go
new file mode 100644
index 0000000..20faaef
--- /dev/null
+++ b/keycloak/openid_user_realm_role_protocol_mapper.go
@@ -0,0 +1,133 @@
+package keycloak
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type OpenIdUserRealmRoleProtocolMapper struct {
+	Id            string
+	Name          string
+	RealmId       string
+	ClientId      string
+	ClientScopeId string
+
+	AddToIdToken     bool
+	AddToAccessToken bool
+	AddToUserInfo    bool
+
+	RealmRolePrefix string
+	Multivalued     bool
+	ClaimName       string
+	ClaimValueType  string
+}
+
+func (mapper *OpenIdUserRealmRoleProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
+	return &protocolMapper{
+		Id:             mapper.Id,
+		Name:           mapper.Name,
+		Protocol:       "openid-connect",
+		ProtocolMapper: "oidc-usermodel-realm-role-mapper",
+		Config: map[string]string{
+			addToIdTokenField:                   strconv.FormatBool(mapper.AddToIdToken),
+			addToAccessTokenField:               strconv.FormatBool(mapper.AddToAccessToken),
+			addToUserInfoField:                  strconv.FormatBool(mapper.AddToUserInfo),
+			claimNameField:                      mapper.ClaimName,
+			claimValueTypeField:                 mapper.ClaimValueType,
+			multivaluedField:                    strconv.FormatBool(mapper.Multivalued),
+			userRealmRoleMappingRolePrefixField: mapper.RealmRolePrefix,
+		},
+	}
+}
+
+func (protocolMapper *protocolMapper) convertToOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId string) (*OpenIdUserRealmRoleProtocolMapper, error) {
+	addToIdToken, err := strconv.ParseBool(protocolMapper.Config[addToIdTokenField])
+	if err != nil {
+		return nil, err
+	}
+
+	addToAccessToken, err := strconv.ParseBool(protocolMapper.Config[addToAccessTokenField])
+	if err != nil {
+		return nil, err
+	}
+
+	addToUserInfo, err := strconv.ParseBool(protocolMapper.Config[addToUserInfoField])
+	if err != nil {
+		return nil, err
+	}
+
+	multivalued, err := strconv.ParseBool(protocolMapper.Config[multivaluedField])
+	if err != nil {
+		return nil, err
+	}
+
+	return &OpenIdUserRealmRoleProtocolMapper{
+		Id:            protocolMapper.Id,
+		Name:          protocolMapper.Name,
+		RealmId:       realmId,
+		ClientId:      clientId,
+		ClientScopeId: clientScopeId,
+
+		AddToIdToken:     addToIdToken,
+		AddToAccessToken: addToAccessToken,
+		AddToUserInfo:    addToUserInfo,
+
+		ClaimName:       protocolMapper.Config[claimNameField],
+		ClaimValueType:  protocolMapper.Config[claimValueTypeField],
+		Multivalued:     multivalued,
+		RealmRolePrefix: protocolMapper.Config[userRealmRoleMappingRolePrefixField],
+	}, nil
+}
+
+func (keycloakClient *KeycloakClient) GetOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId, mapperId string) (*OpenIdUserRealmRoleProtocolMapper, error) {
+	var protocolMapper *protocolMapper
+
+	err := keycloakClient.get(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &protocolMapper, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return protocolMapper.convertToOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId)
+}
+
+func (keycloakClient *KeycloakClient) DeleteOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId, mapperId string) error {
+	return keycloakClient.delete(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), nil)
+}
+
+func (keycloakClient *KeycloakClient) NewOpenIdUserRealmRoleProtocolMapper(mapper *OpenIdUserRealmRoleProtocolMapper) error {
+	path := protocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)
+
+	_, location, err := keycloakClient.post(path, mapper.convertToGenericProtocolMapper())
+	if err != nil {
+		return err
+	}
+
+	mapper.Id = getIdFromLocationHeader(location)
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) UpdateOpenIdUserRealmRoleProtocolMapper(mapper *OpenIdUserRealmRoleProtocolMapper) error {
+	path := individualProtocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)
+
+	return keycloakClient.put(path, mapper.convertToGenericProtocolMapper())
+}
+
+func (keycloakClient *KeycloakClient) ValidateOpenIdUserRealmRoleProtocolMapper(mapper *OpenIdUserRealmRoleProtocolMapper) error {
+	if mapper.ClientId == "" && mapper.ClientScopeId == "" {
+		return fmt.Errorf("validation error: one of ClientId or ClientScopeId must be set")
+	}
+
+	protocolMappers, err := keycloakClient.listGenericProtocolMappers(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)
+	if err != nil {
+		return err
+	}
+
+	for _, protocolMapper := range protocolMappers {
+		if protocolMapper.Name == mapper.Name && protocolMapper.Id != mapper.Id {
+			return fmt.Errorf("validation error: a protocol mapper with name %s already exists for this client", mapper.Name)
+		}
+	}
+
+	return nil
+}
diff --git a/keycloak/protocol_mapper.go b/keycloak/protocol_mapper.go
index 92693e1..a72d57e 100644
--- a/keycloak/protocol_mapper.go
+++ b/keycloak/protocol_mapper.go
@@ -12,21 +12,22 @@ type protocolMapper struct {
 }
 
 var (
-	addToAccessTokenField       = "access.token.claim"
-	addToIdTokenField           = "id.token.claim"
-	addToUserInfoField          = "userinfo.token.claim"
-	attributeNameField          = "attribute.name"
-	attributeNameFormatField    = "attribute.nameformat"
-	claimNameField              = "claim.name"
-	claimValueField             = "claim.value"
-	claimValueTypeField         = "jsonType.label"
-	friendlyNameField           = "friendly.name"
-	fullPathField               = "full.path"
-	includedClientAudienceField = "included.client.audience"
-	includedCustomAudienceField = "included.custom.audience"
-	multivaluedField            = "multivalued"
-	userAttributeField          = "user.attribute"
-	userPropertyField           = "user.attribute"
+	addToAccessTokenField               = "access.token.claim"
+	addToIdTokenField                   = "id.token.claim"
+	addToUserInfoField                  = "userinfo.token.claim"
+	attributeNameField                  = "attribute.name"
+	attributeNameFormatField            = "attribute.nameformat"
+	claimNameField                      = "claim.name"
+	claimValueField                     = "claim.value"
+	claimValueTypeField                 = "jsonType.label"
+	friendlyNameField                   = "friendly.name"
+	fullPathField                       = "full.path"
+	includedClientAudienceField         = "included.client.audience"
+	includedCustomAudienceField         = "included.custom.audience"
+	multivaluedField                    = "multivalued"
+	userAttributeField                  = "user.attribute"
+	userPropertyField                   = "user.attribute"
+	userRealmRoleMappingRolePrefixField = "usermodel.realmRoleMapping.rolePrefix"
 )
 
 func protocolMapperPath(realmId, clientId, clientScopeId string) string {
diff --git a/mkdocs.yml b/mkdocs.yml
index 7433ada..ba7350a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -25,6 +25,7 @@ nav:
   - keycloak_openid_full_name_protocol_mapper: resources/keycloak_openid_full_name_protocol_mapper.md
   - keycloak_openid_audience_protocol_mapper: resources/keycloak_openid_audience_protocol_mapper.md
   - keycloak_openid_hardcoded_role_protocol_mapper: resources/keycloak_openid_hardcoded_role_protocol_mapper.md
+  - keycloak_openid_user_realm_role_protocol_mapper: resources/keycloak_openid_user_realm_role_protocol_mapper.md
   - keycloak_saml_client: resources/keycloak_saml_client.md
   - keycloak_saml_user_attribute_protocol_mapper: resources/keycloak_saml_user_attribute_protocol_mapper.md
   - keycloak_saml_user_property_protocol_mapper: resources/keycloak_saml_user_property_protocol_mapper.md
diff --git a/provider/provider.go b/provider/provider.go
index 7355a5b..06aa3b1 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -36,6 +36,7 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_openid_hardcoded_claim_protocol_mapper":          resourceKeycloakOpenIdHardcodedClaimProtocolMapper(),
 			"keycloak_openid_audience_protocol_mapper":                 resourceKeycloakOpenIdAudienceProtocolMapper(),
 			"keycloak_openid_hardcoded_role_protocol_mapper":           resourceKeycloakOpenIdHardcodedRoleProtocolMapper(),
+			"keycloak_openid_user_realm_role_protocol_mapper":          resourceKeycloakOpenIdUserRealmRoleProtocolMapper(),
 			"keycloak_openid_client_default_scopes":                    resourceKeycloakOpenidClientDefaultScopes(),
 			"keycloak_openid_client_optional_scopes":                   resourceKeycloakOpenidClientOptionalScopes(),
 			"keycloak_saml_client":                                     resourceKeycloakSamlClient(),
diff --git a/provider/resource_keycloak_openid_user_realm_role_protocol_mapper.go b/provider/resource_keycloak_openid_user_realm_role_protocol_mapper.go
new file mode 100644
index 0000000..54ea357
--- /dev/null
+++ b/provider/resource_keycloak_openid_user_realm_role_protocol_mapper.go
@@ -0,0 +1,192 @@
+package provider
+
+import (
+	"github.com/hashicorp/terraform/helper/schema"
+	"github.com/hashicorp/terraform/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func resourceKeycloakOpenIdUserRealmRoleProtocolMapper() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakOpenIdUserRealmRoleProtocolMapperCreate,
+		Read:   resourceKeycloakOpenIdUserRealmRoleProtocolMapperRead,
+		Update: resourceKeycloakOpenIdUserRealmRoleProtocolMapperUpdate,
+		Delete: resourceKeycloakOpenIdUserRealmRoleProtocolMapperDelete,
+		Importer: &schema.ResourceImporter{
+			// import a mapper tied to a client:
+			// {{realmId}}/client/{{clientId}}/{{protocolMapperId}}
+			// or a client scope:
+			// {{realmId}}/client-scope/{{clientScopeId}}/{{protocolMapperId}}
+			State: genericProtocolMapperImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"name": {
+				Type:        schema.TypeString,
+				Required:    true,
+				Description: "A human-friendly name that will appear in the Keycloak console.",
+			},
+			"realm_id": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: "The realm id where the associated client or client scope exists.",
+			},
+			"client_id": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ForceNew:      true,
+				Description:   "The mapper's associated client. Cannot be used at the same time as client_scope_id.",
+				ConflictsWith: []string{"client_scope_id"},
+			},
+			"client_scope_id": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ForceNew:      true,
+				Description:   "The mapper's associated client scope. Cannot be used at the same time as client_id.",
+				ConflictsWith: []string{"client_id"},
+			},
+			"add_to_id_token": {
+				Type:        schema.TypeBool,
+				Optional:    true,
+				Default:     true,
+				Description: "Indicates if the attribute should be a claim in the id token.",
+			},
+			"add_to_access_token": {
+				Type:        schema.TypeBool,
+				Optional:    true,
+				Default:     true,
+				Description: "Indicates if the attribute should be a claim in the access token.",
+			},
+			"add_to_userinfo": {
+				Type:        schema.TypeBool,
+				Optional:    true,
+				Default:     true,
+				Description: "Indicates if the attribute should appear in the userinfo response body.",
+			},
+			"claim_name": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"claim_value_type": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Description:  "Claim type used when serializing tokens.",
+				Default:      "String",
+				ValidateFunc: validation.StringInSlice([]string{"String", "long", "int", "boolean"}, true),
+			},
+			"multivalued": {
+				Type:        schema.TypeBool,
+				Optional:    true,
+				Default:     false,
+				Description: "Indicates whether this attribute is a single value or an array of values.",
+			},
+			"realm_role_prefix": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				Description: "Prefix that will be added to each realm role.",
+			},
+		},
+	}
+}
+
+func mapFromDataToOpenIdUserRealmRoleProtocolMapper(data *schema.ResourceData) *keycloak.OpenIdUserRealmRoleProtocolMapper {
+	return &keycloak.OpenIdUserRealmRoleProtocolMapper{
+		Id:               data.Id(),
+		Name:             data.Get("name").(string),
+		RealmId:          data.Get("realm_id").(string),
+		ClientId:         data.Get("client_id").(string),
+		ClientScopeId:    data.Get("client_scope_id").(string),
+		AddToIdToken:     data.Get("add_to_id_token").(bool),
+		AddToAccessToken: data.Get("add_to_access_token").(bool),
+		AddToUserInfo:    data.Get("add_to_userinfo").(bool),
+
+		ClaimName:       data.Get("claim_name").(string),
+		ClaimValueType:  data.Get("claim_value_type").(string),
+		RealmRolePrefix: data.Get("realm_role_prefix").(string),
+		Multivalued:     data.Get("multivalued").(bool),
+	}
+}
+
+func mapFromOpenIdUserRealmRoleMapperToData(mapper *keycloak.OpenIdUserRealmRoleProtocolMapper, data *schema.ResourceData) {
+	data.SetId(mapper.Id)
+	data.Set("name", mapper.Name)
+	data.Set("realm_id", mapper.RealmId)
+
+	if mapper.ClientId != "" {
+		data.Set("client_id", mapper.ClientId)
+	} else {
+		data.Set("client_scope_id", mapper.ClientScopeId)
+	}
+
+	data.Set("add_to_id_token", mapper.AddToIdToken)
+	data.Set("add_to_access_token", mapper.AddToAccessToken)
+	data.Set("add_to_userinfo", mapper.AddToUserInfo)
+	data.Set("claim_name", mapper.ClaimName)
+	data.Set("claim_value_type", mapper.ClaimValueType)
+	data.Set("realm_role_prefix", mapper.RealmRolePrefix)
+	data.Set("multivalued", mapper.Multivalued)
+}
+
+func resourceKeycloakOpenIdUserRealmRoleProtocolMapperCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	openIdUserRealmRoleMapper := mapFromDataToOpenIdUserRealmRoleProtocolMapper(data)
+
+	err := keycloakClient.ValidateOpenIdUserRealmRoleProtocolMapper(openIdUserRealmRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.NewOpenIdUserRealmRoleProtocolMapper(openIdUserRealmRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	mapFromOpenIdUserRealmRoleMapperToData(openIdUserRealmRoleMapper, data)
+
+	return resourceKeycloakOpenIdUserRealmRoleProtocolMapperRead(data, meta)
+}
+
+func resourceKeycloakOpenIdUserRealmRoleProtocolMapperRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	clientScopeId := data.Get("client_scope_id").(string)
+
+	openIdUserRealmRoleMapper, err := keycloakClient.GetOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId, data.Id())
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	mapFromOpenIdUserRealmRoleMapperToData(openIdUserRealmRoleMapper, data)
+
+	return nil
+}
+
+func resourceKeycloakOpenIdUserRealmRoleProtocolMapperUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	openIdUserRealmRoleMapper := mapFromDataToOpenIdUserRealmRoleProtocolMapper(data)
+
+	err := keycloakClient.ValidateOpenIdUserRealmRoleProtocolMapper(openIdUserRealmRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.UpdateOpenIdUserRealmRoleProtocolMapper(openIdUserRealmRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	return resourceKeycloakOpenIdUserRealmRoleProtocolMapperRead(data, meta)
+}
+
+func resourceKeycloakOpenIdUserRealmRoleProtocolMapperDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	clientScopeId := data.Get("client_scope_id").(string)
+
+	return keycloakClient.DeleteOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId, data.Id())
+}
diff --git a/provider/resource_keycloak_realm.go b/provider/resource_keycloak_realm.go
index bc512f9..bf3e899 100644
--- a/provider/resource_keycloak_realm.go
+++ b/provider/resource_keycloak_realm.go
@@ -339,7 +339,7 @@ func resourceKeycloakRealm() *schema.Resource {
 				Type:        schema.TypeString,
 				Description: "Which flow should be used for ResetCredentialsFlow",
 				Optional:    true,
-				Default:     "registration",
+				Default:     "reset credentials",
 			},
 			"client_authentication_flow": {
 				Type:        schema.TypeString,


Test patch:
diff --git a/provider/generic_protocol_mapper_validation_test.go b/provider/generic_protocol_mapper_validation_test.go
index d6a359e..a3e54e9 100644
--- a/provider/generic_protocol_mapper_validation_test.go
+++ b/provider/generic_protocol_mapper_validation_test.go
@@ -2,10 +2,11 @@ package provider
 
 import (
 	"fmt"
-	"github.com/hashicorp/terraform/helper/acctest"
-	"github.com/hashicorp/terraform/helper/resource"
 	"regexp"
 	"testing"
+
+	"github.com/hashicorp/terraform/helper/acctest"
+	"github.com/hashicorp/terraform/helper/resource"
 )
 
 /*
@@ -256,6 +257,30 @@ func TestAccKeycloakOpenIdHardcodedClaimProtocolMapper_clientScopeDuplicateNameV
 	})
 }
 
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_clientScopeDuplicateNameValidation(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-user-realm-role-mapper-" + acctest.RandString(5)
+
+	fullNameProtocolMapperResourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testGenericProtocolMapperValidation_clientScopeUserRealmRoleMapper(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(fullNameProtocolMapperResourceName),
+			},
+			{
+				Config:      testGenericProtocolMapperValidation_clientScopeUserRealmRoleAndHardcodedClaimMapper(realmName, clientId, mapperName),
+				ExpectError: regexp.MustCompile("validation error: a protocol mapper with name .+ already exists for this client"),
+			},
+		},
+	})
+}
+
 /*
  * Protocol mappers must be attached to either a client or client scope.  The following tests assert that errors are raised
  * if neither are specified.
@@ -341,6 +366,22 @@ func TestAccKeycloakOpenIdHardcodedClaimProtocolMapper_validateClientOrClientSco
 	})
 }
 
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_validateClientOrClientScopeSet(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakOpenIdUserRealmRoleProtocolMapper_parentResourceValidation(realmName, mapperName),
+				ExpectError: regexp.MustCompile("validation error: one of ClientId or ClientScopeId must be set"),
+			},
+		},
+	})
+}
+
 func testGenericProtocolMapperValidation_clientGroupMembershipMapper(realmName, clientId, mapperName string) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
@@ -566,6 +607,29 @@ resource "keycloak_openid_group_membership_protocol_mapper" "group_membership_ma
 }`, realmName, clientScopeId, mapperName)
 }
 
+func testGenericProtocolMapperValidation_clientUserRealmRoleMapper(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id    = "${keycloak_realm.realm.id}"
+	client_id   = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_id        = "${keycloak_openid_client.openid_client.id}"
+
+	claim_name       = "foo"
+	claim_value_type = "String"
+}`, realmName, clientId, mapperName)
+}
+
 func testGenericProtocolMapperValidation_clientScopeFullNameMapper(realmName, clientId, mapperName string) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
@@ -624,6 +688,25 @@ resource "keycloak_openid_user_property_protocol_mapper" "user_property_mapper_c
 }`, realmName, clientId, mapperName)
 }
 
+func testGenericProtocolMapperValidation_clientScopeUserRealmRoleMapper(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client_scope" {
+	name            = "%s"
+	realm_id        = "${keycloak_realm.realm.id}"
+	client_scope_id = "${keycloak_openid_client_scope.client_scope.id}"
+	claim_name      = "bar-property"
+}`, realmName, clientId, mapperName)
+
+}
 func testGenericProtocolMapperValidation_clientScopeFullNameAndGroupMembershipMapper(realmName, clientScopeId, mapperName string) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
@@ -734,6 +817,35 @@ resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_mapp
 }`, realmName, clientId, mapperName, mapperName)
 }
 
+func testGenericProtocolMapperValidation_clientScopeUserRealmRoleAndHardcodedClaimMapper(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client_scope" {
+	name            = "%s"
+	realm_id        = "${keycloak_realm.realm.id}"
+	client_scope_id = "${keycloak_openid_client_scope.client_scope.id}"
+	claim_name      = "bar-property"
+}
+
+resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_mapper_client_scope" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_scope_id  = "${keycloak_openid_client_scope.client_scope.id}"
+
+	claim_name       = "foo"
+	claim_value      = "bar"
+	claim_value_type = "String"
+}`, realmName, clientId, mapperName, mapperName)
+}
+
 func testKeycloakOpenIdFullNameProtocolMapper_parentResourceValidation(realmName, mapperName string) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
@@ -802,3 +914,18 @@ resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_mapp
 	claim_value_type = "String"
 }`, realmName, mapperName)
 }
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapper_parentResourceValidation(realmName, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client_scope" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+
+	claim_name       = "foo"
+	claim_value_type = "String"
+}`, realmName, mapperName)
+}
diff --git a/provider/resource_keycloak_openid_user_realm_role_protocol_mapper_test.go b/provider/resource_keycloak_openid_user_realm_role_protocol_mapper_test.go
new file mode 100644
index 0000000..dc5f919
--- /dev/null
+++ b/provider/resource_keycloak_openid_user_realm_role_protocol_mapper_test.go
@@ -0,0 +1,432 @@
+package provider
+
+import (
+	"fmt"
+	"regexp"
+	"testing"
+
+	"github.com/hashicorp/terraform/helper/acctest"
+	"github.com/hashicorp/terraform/helper/resource"
+	"github.com/hashicorp/terraform/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_basicClient(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_client(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_basicClientScope(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_clientScope(realmName, clientScopeId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_import(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-openid-client-" + acctest.RandString(10)
+	clientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	clientResourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client"
+	clientScopeResourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdFullNameProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_import(realmName, clientId, clientScopeId, mapperName),
+				Check: resource.ComposeTestCheckFunc(
+					testKeycloakOpenIdUserRealmRoleProtocolMapperExists(clientResourceName),
+					testKeycloakOpenIdUserRealmRoleProtocolMapperExists(clientScopeResourceName),
+				),
+			},
+			{
+				ResourceName:      clientResourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getGenericProtocolMapperIdForClient(clientResourceName),
+			},
+			{
+				ResourceName:      clientScopeResourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getGenericProtocolMapperIdForClientScope(clientScopeResourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_update(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	claimName := "claim-name-" + acctest.RandString(10)
+	updatedClaimName := "claim-name-update-" + acctest.RandString(10)
+
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(realmName, clientId, mapperName, claimName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(realmName, clientId, mapperName, updatedClaimName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.OpenIdUserRealmRoleProtocolMapper{}
+
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_client(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperFetch(resourceName, mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteOpenIdUserRealmRoleProtocolMapper(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)
+					if err != nil {
+						t.Error(err)
+					}
+				},
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_client(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_validateClaimValueType(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(10)
+	invalidClaimValueType := acctest.RandString(5)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakOpenIdUserRealmRoleProtocolMapper_validateClaimValueType(realmName, mapperName, invalidClaimValueType),
+				ExpectError: regexp.MustCompile("expected claim_value_type to be one of .+ got " + invalidClaimValueType),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_updateClientIdForceNew(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	updatedClientId := "terraform-client-update-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	claimName := "claim-name-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(realmName, clientId, mapperName, claimName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(realmName, updatedClientId, mapperName, claimName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_updateClientScopeForceNew(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+	clientScopeId := "terraform-client-" + acctest.RandString(10)
+	newClientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_clientScope(realmName, clientScopeId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_clientScope(realmName, newClientScopeId, mapperName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdUserRealmRoleProtocolMapper_updateRealmIdForceNew(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	newRealmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-user-realm-role-mapper-" + acctest.RandString(5)
+
+	claimName := "claim-name-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(realmName, clientId, mapperName, claimName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(newRealmName, clientId, mapperName, claimName),
+				Check:  testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func testAccKeycloakOpenIdUserRealmRoleProtocolMapperDestroy() resource.TestCheckFunc {
+	return func(state *terraform.State) error {
+		for resourceName, rs := range state.RootModule().Resources {
+			if rs.Type != "keycloak_openid_user_realm_role_protocol_mapper" {
+				continue
+			}
+
+			mapper, _ := getUserRealmRoleMapperUsingState(state, resourceName)
+
+			if mapper != nil {
+				return fmt.Errorf("openid user attribute protocol mapper with id %s still exists", rs.Primary.ID)
+			}
+		}
+
+		return nil
+	}
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapperExists(resourceName string) resource.TestCheckFunc {
+	return func(state *terraform.State) error {
+		_, err := getUserRealmRoleMapperUsingState(state, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapperFetch(resourceName string, mapper *keycloak.OpenIdUserRealmRoleProtocolMapper) resource.TestCheckFunc {
+	return func(state *terraform.State) error {
+		fetchedMapper, err := getUserRealmRoleMapperUsingState(state, resourceName)
+		if err != nil {
+			return err
+		}
+
+		mapper.Id = fetchedMapper.Id
+		mapper.ClientId = fetchedMapper.ClientId
+		mapper.ClientScopeId = fetchedMapper.ClientScopeId
+		mapper.RealmId = fetchedMapper.RealmId
+
+		return nil
+	}
+}
+
+func getUserRealmRoleMapperUsingState(state *terraform.State, resourceName string) (*keycloak.OpenIdUserRealmRoleProtocolMapper, error) {
+	rs, ok := state.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found in TF state: %s ", resourceName)
+	}
+
+	id := rs.Primary.ID
+	realm := rs.Primary.Attributes["realm_id"]
+	clientId := rs.Primary.Attributes["client_id"]
+	clientScopeId := rs.Primary.Attributes["client_scope_id"]
+
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	return keycloakClient.GetOpenIdUserRealmRoleProtocolMapper(realm, clientId, clientScopeId, id)
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_client(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_id        = "${keycloak_openid_client.openid_client.id}"
+
+	claim_name       = "foo"
+	claim_value_type = "String"
+}`, realmName, clientId, mapperName)
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapper_basic_clientScope(realmName, clientScopeId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client_scope" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_scope_id  = "${keycloak_openid_client_scope.client_scope.id}"
+
+	claim_name       = "foo"
+	claim_value_type = "String"
+}`, realmName, clientScopeId, mapperName)
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapper_claim(realmName, clientId, mapperName, claimName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_id        = "${keycloak_openid_client.openid_client.id}"
+
+	claim_name       = "%s"
+	claim_value_type = "String"
+}`, realmName, clientId, mapperName, claimName)
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapper_import(realmName, clientId, clientScopeId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id    = "${keycloak_realm.realm.id}"
+	client_id   = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_id        = "${keycloak_openid_client.openid_client.id}"
+
+	claim_name       = "foo"
+	claim_value_type = "String"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_client_scope" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_scope_id  = "${keycloak_openid_client_scope.client_scope.id}"
+
+	claim_name       = "foo"
+	claim_value_type = "String"
+}`, realmName, clientId, mapperName, clientScopeId, mapperName)
+}
+
+func testKeycloakOpenIdUserRealmRoleProtocolMapper_validateClaimValueType(realmName, mapperName, claimValueType string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "openid-client"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper_validation" {
+	name             = "%s"
+	realm_id         = "${keycloak_realm.realm.id}"
+	client_id        = "${keycloak_openid_client.openid_client.id}"
+
+	claim_name      = "foo"
+	claim_value_type = "%s"
+}`, realmName, mapperName, claimValueType)
+}