Repository: mrparkers/terraform-provider-keycloak
Pull request #265 (2020-04-01 18:17:17)

feat: add ldap role mapper
add ldap role mapper

Trois-Six: Hi, what do you think about this PR ?

Patch:
diff --git a/docs/data_sources/keycloak_saml_client_installation_provider.md b/docs/data_sources/keycloak_saml_client_installation_provider.md
new file mode 100644
index 0000000..51ec9e6
--- /dev/null
+++ b/docs/data_sources/keycloak_saml_client_installation_provider.md
@@ -0,0 +1,56 @@
+# keycloak_saml_client_installation_provider data source
+
+This data source can be used to retrieve Installation Provider
+of a SAML Client.
+
+### Example Usage
+
+In the example below, we extract the SAML metadata IDPSSODescriptor 
+to pass it to the AWS IAM SAML Provider.
+
+```hcl
+resource "keycloak_realm" "realm" {
+    realm   = "my-realm"
+    enabled = true
+}
+
+resource "keycloak_saml_client" "saml_client" {
+    realm_id                = "${keycloak_realm.realm.id}"
+    client_id               = "test-saml-client"
+    name                    = "test-saml-client"
+
+    sign_documents          = false
+    sign_assertions         = true
+    include_authn_statement = true
+
+    signing_certificate = "${file("saml-cert.pem")}"
+    signing_private_key = "${file("saml-key.pem")}"
+}
+
+data "keycloak_saml_client_installation_provider" "saml_idp_descriptor" {
+  realm_id    = "${keycloak_realm.realm.id}"
+  client_id   = "${keycloak_saml_client.saml_client}"
+  provider_id = "saml-idp-descriptor"
+}
+
+
+resource "aws_iam_saml_provider" "default" {
+  name                   = "myprovider"
+  saml_metadata_document = data.keycloak_saml_client_installation_provider.saml_idp_descriptor.value
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this group exists within.
+- `client_id` - (Required) The name of the saml client. Not the id of the client.
+- `provider_id` - (Required) Could be one of `saml-idp-descriptor`, `keycloak-saml`, `saml-sp-descriptor`, `keycloak-saml-subsystem`, `mod-auth-mellon`
+
+### Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+- `id` - The hash of the value
+- `value` The returned XML document needed for SAML installation
diff --git a/keycloak/keycloak_client.go b/keycloak/keycloak_client.go
index b2c04ea..570b1dd 100644
--- a/keycloak/keycloak_client.go
+++ b/keycloak/keycloak_client.go
@@ -296,11 +296,19 @@ func (keycloakClient *KeycloakClient) sendRequest(request *http.Request) ([]byte
 }
 
 func (keycloakClient *KeycloakClient) get(path string, resource interface{}, params map[string]string) error {
+	body, err := keycloakClient.getRaw(path, params)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(body, resource)
+}
+
+func (keycloakClient *KeycloakClient) getRaw(path string, params map[string]string) ([]byte, error) {
 	resourceUrl := keycloakClient.baseUrl + apiUrl + path
 
 	request, err := http.NewRequest(http.MethodGet, resourceUrl, nil)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	if params != nil {
@@ -312,11 +320,7 @@ func (keycloakClient *KeycloakClient) get(path string, resource interface{}, par
 	}
 
 	body, _, err := keycloakClient.sendRequest(request)
-	if err != nil {
-		return err
-	}
-
-	return json.Unmarshal(body, resource)
+	return body, err
 }
 
 func (keycloakClient *KeycloakClient) post(path string, requestBody interface{}) ([]byte, string, error) {
diff --git a/keycloak/ldap_role_mapper.go b/keycloak/ldap_role_mapper.go
new file mode 100644
index 0000000..b5a055e
--- /dev/null
+++ b/keycloak/ldap_role_mapper.go
@@ -0,0 +1,153 @@
+package keycloak
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+type LdapRoleMapper struct {
+	Id                   string
+	Name                 string
+	RealmId              string
+	LdapUserFederationId string
+
+	LdapRolesDn                 string
+	RoleNameLdapAttribute       string
+	RoleObjectClasses           []string
+	MembershipLdapAttribute     string
+	MembershipAttributeType     string
+	MembershipUserLdapAttribute string
+	RolesLdapFilter             string
+	Mode                        string
+	UserRolesRetrieveStrategy   string
+	MemberofLdapAttribute       string
+	UseRealmRolesMapping        bool
+	ClientId                    string
+}
+
+func convertFromLdapRoleMapperToComponent(ldapRoleMapper *LdapRoleMapper) *component {
+	componentConfig := map[string][]string{
+		"roles.dn": {
+			ldapRoleMapper.LdapRolesDn,
+		},
+		"role.name.ldap.attribute": {
+			ldapRoleMapper.RoleNameLdapAttribute,
+		},
+		"role.object.classes": {
+			strings.Join(ldapRoleMapper.RoleObjectClasses, ","),
+		},
+		"membership.ldap.attribute": {
+			ldapRoleMapper.MembershipLdapAttribute,
+		},
+		"membership.attribute.type": {
+			ldapRoleMapper.MembershipAttributeType,
+		},
+		"membership.user.ldap.attribute": {
+			ldapRoleMapper.MembershipUserLdapAttribute,
+		},
+		"mode": {
+			ldapRoleMapper.Mode,
+		},
+		"user.roles.retrieve.strategy": {
+			ldapRoleMapper.UserRolesRetrieveStrategy,
+		},
+		"memberof.ldap.attribute": {
+			ldapRoleMapper.MemberofLdapAttribute,
+		},
+		"use.realm.roles.mapping": {
+			strconv.FormatBool(ldapRoleMapper.UseRealmRolesMapping),
+		},
+	}
+
+	if ldapRoleMapper.RolesLdapFilter != "" {
+		componentConfig["roles.ldap.filter"] = []string{ldapRoleMapper.RolesLdapFilter}
+	}
+
+	if ldapRoleMapper.ClientId != "" {
+		componentConfig["client.id"] = []string{ldapRoleMapper.ClientId}
+	}
+
+	return &component{
+		Id:           ldapRoleMapper.Id,
+		Name:         ldapRoleMapper.Name,
+		ProviderId:   "role-ldap-mapper",
+		ProviderType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
+		ParentId:     ldapRoleMapper.LdapUserFederationId,
+		Config:       componentConfig,
+	}
+}
+
+func convertFromComponentToLdapRoleMapper(component *component, realmId string) (*LdapRoleMapper, error) {
+	roleObjectClasses := strings.Split(component.getConfig("role.object.classes"), ",")
+	for i, v := range roleObjectClasses {
+		roleObjectClasses[i] = strings.TrimSpace(v)
+	}
+
+	useRealmRolesMapping, err := parseBoolAndTreatEmptyStringAsFalse(component.getConfig("use.realm.roles.mapping"))
+	if err != nil {
+		return nil, err
+	}
+
+	ldapRoleMapper := &LdapRoleMapper{
+		Id:                   component.Id,
+		Name:                 component.Name,
+		RealmId:              realmId,
+		LdapUserFederationId: component.ParentId,
+
+		LdapRolesDn:                 component.getConfig("roles.dn"),
+		RoleNameLdapAttribute:       component.getConfig("role.name.ldap.attribute"),
+		RoleObjectClasses:           roleObjectClasses,
+		MembershipLdapAttribute:     component.getConfig("membership.ldap.attribute"),
+		MembershipAttributeType:     component.getConfig("membership.attribute.type"),
+		MembershipUserLdapAttribute: component.getConfig("membership.user.ldap.attribute"),
+		Mode:                        component.getConfig("mode"),
+		UserRolesRetrieveStrategy:   component.getConfig("user.roles.retrieve.strategy"),
+		MemberofLdapAttribute:       component.getConfig("memberof.ldap.attribute"),
+		UseRealmRolesMapping:        useRealmRolesMapping,
+	}
+
+	if rolesLdapFilter := component.getConfig("roles.ldap.filter"); rolesLdapFilter != "" {
+		ldapRoleMapper.RolesLdapFilter = rolesLdapFilter
+	}
+
+	if clientId := component.getConfig("client.id"); clientId != "" {
+		ldapRoleMapper.ClientId = clientId
+	}
+
+	return ldapRoleMapper, nil
+}
+
+func (keycloakClient *KeycloakClient) ValidateLdapRoleMapper(ldapRoleMapper *LdapRoleMapper) error {
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) NewLdapRoleMapper(ldapRoleMapper *LdapRoleMapper) error {
+	_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/components", ldapRoleMapper.RealmId), convertFromLdapRoleMapperToComponent(ldapRoleMapper))
+	if err != nil {
+		return err
+	}
+
+	ldapRoleMapper.Id = getIdFromLocationHeader(location)
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) GetLdapRoleMapper(realmId, id string) (*LdapRoleMapper, error) {
+	var component *component
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/components/%s", realmId, id), &component, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return convertFromComponentToLdapRoleMapper(component, realmId)
+}
+
+func (keycloakClient *KeycloakClient) UpdateLdapRoleMapper(ldapRoleMapper *LdapRoleMapper) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/components/%s", ldapRoleMapper.RealmId, ldapRoleMapper.Id), convertFromLdapRoleMapperToComponent(ldapRoleMapper))
+}
+
+func (keycloakClient *KeycloakClient) DeleteLdapRoleMapper(realmId, id string) error {
+	return keycloakClient.delete(fmt.Sprintf("/realms/%s/components/%s", realmId, id), nil)
+}
diff --git a/keycloak/ldap_user_federation.go b/keycloak/ldap_user_federation.go
index 15cf6c9..ae95541 100644
--- a/keycloak/ldap_user_federation.go
+++ b/keycloak/ldap_user_federation.go
@@ -371,7 +371,11 @@ func (keycloakClient *KeycloakClient) GetLdapUserFederationMappers(realmId, id s
 			}
 			ldapUserFederationMappers = append(ldapUserFederationMappers, mapper)
 		case "role-ldap-mapper":
-			// Not supported
+			mapper, err := convertFromComponentToLdapRoleMapper(component, realmId)
+			if err != nil {
+				return nil, err
+			}
+			ldapUserFederationMappers = append(ldapUserFederationMappers, mapper)
 		}
 	}
 
diff --git a/keycloak/saml_client.go b/keycloak/saml_client.go
index 4445a38..4e3b848 100644
--- a/keycloak/saml_client.go
+++ b/keycloak/saml_client.go
@@ -73,6 +73,11 @@ func (keycloakClient *KeycloakClient) GetSamlClient(realmId, id string) (*SamlCl
 	return &client, nil
 }
 
+func (keycloakClient *KeycloakClient) GetSamlClientInstallationProvider(realmId, id string, providerId string) ([]byte, error) {
+	value, err := keycloakClient.getRaw(fmt.Sprintf("/realms/%s/clients/%s/installation/providers/%s", realmId, id, providerId), nil)
+	return value, err
+}
+
 func (keycloakClient *KeycloakClient) UpdateSamlClient(client *SamlClient) error {
 	client.Protocol = "saml"
 	client.ClientAuthenticatorType = "client-secret"
diff --git a/provider/data_source_keycloak_saml_client_installation_provider.go b/provider/data_source_keycloak_saml_client_installation_provider.go
new file mode 100644
index 0000000..d97ae55
--- /dev/null
+++ b/provider/data_source_keycloak_saml_client_installation_provider.go
@@ -0,0 +1,57 @@
+package provider
+
+import (
+	"crypto/sha1"
+	"encoding/base64"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func dataSourceKeycloakSamlClientInstallationProvider() *schema.Resource {
+	return &schema.Resource{
+		Read: dataSourceKeycloakSamlClientInstallationProviderRead,
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"client_id": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"provider_id": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"value": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+		},
+	}
+}
+
+func dataSourceKeycloakSamlClientInstallationProviderRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	cliendId := data.Get("client_id").(string)
+	providerId := data.Get("provider_id").(string)
+
+	value, err := keycloakClient.GetSamlClientInstallationProvider(realmId, cliendId, providerId)
+	if err != nil {
+		return err
+	}
+
+	h := sha1.New()
+	h.Write(value)
+	id := base64.URLEncoding.EncodeToString(h.Sum(nil))
+
+	data.SetId(id)
+	data.Set("realm_id", realmId)
+	data.Set("client_id", cliendId)
+	data.Set("provider_id", providerId)
+	data.Set("value", string(value))
+
+	return nil
+}
diff --git a/provider/provider.go b/provider/provider.go
index 319cbac..f9dcb68 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -15,6 +15,7 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_realm":                              dataSourceKeycloakRealm(),
 			"keycloak_realm_keys":                         dataSourceKeycloakRealmKeys(),
 			"keycloak_role":                               dataSourceKeycloakRole(),
+			"keycloak_saml_client_installation_provider":  dataSourceKeycloakSamlClientInstallationProvider(),
 		},
 		ResourcesMap: map[string]*schema.Resource{
 			"keycloak_realm":                                           resourceKeycloakRealm(),
@@ -30,6 +31,7 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_ldap_user_federation":                            resourceKeycloakLdapUserFederation(),
 			"keycloak_ldap_user_attribute_mapper":                      resourceKeycloakLdapUserAttributeMapper(),
 			"keycloak_ldap_group_mapper":                               resourceKeycloakLdapGroupMapper(),
+			"keycloak_ldap_role_mapper":                                resourceKeycloakLdapRoleMapper(),
 			"keycloak_ldap_hardcoded_role_mapper":                      resourceKeycloakLdapHardcodedRoleMapper(),
 			"keycloak_ldap_hardcoded_group_mapper":                     resourceKeycloakLdapHardcodedGroupMapper(),
 			"keycloak_ldap_msad_user_account_control_mapper":           resourceKeycloakLdapMsadUserAccountControlMapper(),
diff --git a/provider/resource_keycloak_ldap_role_mapper.go b/provider/resource_keycloak_ldap_role_mapper.go
new file mode 100644
index 0000000..c91f4cd
--- /dev/null
+++ b/provider/resource_keycloak_ldap_role_mapper.go
@@ -0,0 +1,219 @@
+package provider
+
+import (
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"regexp"
+)
+
+var (
+	keycloakLdapRoleMapperModes                       = []string{"READ_ONLY", "LDAP_ONLY"}
+	keycloakLdapRoleMapperMembershipAttributeTypes    = []string{"DN", "UID"}
+	keycloakLdapRoleMapperUserRolesRetrieveStrategies = []string{"LOAD_ROLES_BY_MEMBER_ATTRIBUTE", "GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE", "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"}
+)
+
+func resourceKeycloakLdapRoleMapper() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakLdapRoleMapperCreate,
+		Read:   resourceKeycloakLdapRoleMapperRead,
+		Update: resourceKeycloakLdapRoleMapperUpdate,
+		Delete: resourceKeycloakLdapRoleMapperDelete,
+		// This resource can be imported using {{realm}}/{{provider_id}}/{{mapper_id}}. The Provider and Mapper IDs are displayed in the GUI
+		Importer: &schema.ResourceImporter{
+			State: 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.",
+			},
+			"ldap_roles_dn": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"role_name_ldap_attribute": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"role_object_classes": {
+				Type:     schema.TypeList,
+				Required: true,
+				MinItems: 1,
+				Elem:     &schema.Schema{Type: schema.TypeString},
+			},
+			"membership_ldap_attribute": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"membership_attribute_type": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Default:      "DN",
+				ValidateFunc: validation.StringInSlice(keycloakLdapRoleMapperMembershipAttributeTypes, false),
+			},
+			"membership_user_ldap_attribute": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"roles_ldap_filter": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				ValidateFunc: validation.StringMatch(regexp.MustCompile(`\(.+\)`), "validation error: roles ldap filter must start with '(' and end with ')'"),
+			},
+			"mode": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Default:      "READ_ONLY",
+				ValidateFunc: validation.StringInSlice(keycloakLdapRoleMapperModes, false),
+			},
+			"user_roles_retrieve_strategy": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Default:      "LOAD_ROLES_BY_MEMBER_ATTRIBUTE",
+				ValidateFunc: validation.StringInSlice(keycloakLdapRoleMapperUserRolesRetrieveStrategies, false),
+			},
+			"memberof_ldap_attribute": {
+				Type:     schema.TypeString,
+				Optional: true,
+				Default:  "memberOf",
+			},
+			"use_realm_roles_mapping": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  true,
+			},
+			"client_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+		},
+	}
+}
+
+func getLdapRoleMapperFromData(data *schema.ResourceData) *keycloak.LdapRoleMapper {
+	var roleObjectClasses []string
+
+	for _, roleObjectClass := range data.Get("role_object_classes").([]interface{}) {
+		roleObjectClasses = append(roleObjectClasses, roleObjectClass.(string))
+	}
+
+	return &keycloak.LdapRoleMapper{
+		Id:                   data.Id(),
+		Name:                 data.Get("name").(string),
+		RealmId:              data.Get("realm_id").(string),
+		LdapUserFederationId: data.Get("ldap_user_federation_id").(string),
+
+		LdapRolesDn:                 data.Get("ldap_roles_dn").(string),
+		RoleNameLdapAttribute:       data.Get("role_name_ldap_attribute").(string),
+		RoleObjectClasses:           roleObjectClasses,
+		MembershipLdapAttribute:     data.Get("membership_ldap_attribute").(string),
+		MembershipAttributeType:     data.Get("membership_attribute_type").(string),
+		MembershipUserLdapAttribute: data.Get("membership_user_ldap_attribute").(string),
+		RolesLdapFilter:             data.Get("roles_ldap_filter").(string),
+		Mode:                        data.Get("mode").(string),
+		UserRolesRetrieveStrategy:   data.Get("user_roles_retrieve_strategy").(string),
+		MemberofLdapAttribute:       data.Get("memberof_ldap_attribute").(string),
+		UseRealmRolesMapping:        data.Get("use_realm_roles_mapping").(bool),
+		ClientId:                    data.Get("client_id").(string),
+	}
+}
+
+func setLdapRoleMapperData(data *schema.ResourceData, ldapRoleMapper *keycloak.LdapRoleMapper) {
+	data.SetId(ldapRoleMapper.Id)
+
+	data.Set("name", ldapRoleMapper.Name)
+	data.Set("realm_id", ldapRoleMapper.RealmId)
+	data.Set("ldap_user_federation_id", ldapRoleMapper.LdapUserFederationId)
+
+	data.Set("ldap_roles_dn", ldapRoleMapper.LdapRolesDn)
+	data.Set("role_name_ldap_attribute", ldapRoleMapper.RoleNameLdapAttribute)
+	data.Set("role_object_classes", ldapRoleMapper.RoleObjectClasses)
+	data.Set("membership_ldap_attribute", ldapRoleMapper.MembershipLdapAttribute)
+	data.Set("membership_attribute_type", ldapRoleMapper.MembershipAttributeType)
+	data.Set("membership_user_ldap_attribute", ldapRoleMapper.MembershipUserLdapAttribute)
+	data.Set("roles_ldap_filter", ldapRoleMapper.RolesLdapFilter)
+	data.Set("mode", ldapRoleMapper.Mode)
+	data.Set("user_roles_retrieve_strategy", ldapRoleMapper.UserRolesRetrieveStrategy)
+	data.Set("memberof_ldap_attribute", ldapRoleMapper.MemberofLdapAttribute)
+	data.Set("use_realm_roles_mapping", ldapRoleMapper.UseRealmRolesMapping)
+	data.Set("client_id", ldapRoleMapper.ClientId)
+}
+
+func resourceKeycloakLdapRoleMapperCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	ldapRoleMapper := getLdapRoleMapperFromData(data)
+
+	err := keycloakClient.ValidateLdapRoleMapper(ldapRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.NewLdapRoleMapper(ldapRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	setLdapRoleMapperData(data, ldapRoleMapper)
+
+	return resourceKeycloakLdapRoleMapperRead(data, meta)
+}
+
+func resourceKeycloakLdapRoleMapperRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	ldapRoleMapper, err := keycloakClient.GetLdapRoleMapper(realmId, id)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	setLdapRoleMapperData(data, ldapRoleMapper)
+
+	return nil
+}
+
+func resourceKeycloakLdapRoleMapperUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	ldapRoleMapper := getLdapRoleMapperFromData(data)
+
+	err := keycloakClient.ValidateLdapRoleMapper(ldapRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.UpdateLdapRoleMapper(ldapRoleMapper)
+	if err != nil {
+		return err
+	}
+
+	setLdapRoleMapperData(data, ldapRoleMapper)
+
+	return nil
+}
+
+func resourceKeycloakLdapRoleMapperDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	return keycloakClient.DeleteLdapRoleMapper(realmId, id)
+}


Test patch:
diff --git a/provider/data_source_keycloak_saml_client_installation_provider_test.go b/provider/data_source_keycloak_saml_client_installation_provider_test.go
new file mode 100644
index 0000000..dfa76c5
--- /dev/null
+++ b/provider/data_source_keycloak_saml_client_installation_provider_test.go
@@ -0,0 +1,73 @@
+package provider
+
+import (
+	"encoding/xml"
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/terraform"
+)
+
+func TestAccKeycloakSamlClientInstallationProvider_basic(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientId := "terraform-" + acctest.RandString(10)
+
+	resourceName := "keycloak_saml_client.saml_client"
+	dataSourceName := "data.keycloak_saml_client_installation_provider.saml_sp_descriptor"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakSamlClientDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testDataSourceKeycloakSamlClientInstallationProvider_basic(realmName, clientId),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttrPair(dataSourceName, "realm_id", resourceName, "realm_id"),
+					resource.TestCheckResourceAttrPair(dataSourceName, "client_id", resourceName, "id"),
+					resource.TestCheckResourceAttr(dataSourceName, "provider_id", "saml-sp-descriptor"),
+					testAccCheckDataKeycloakSamlClientInstallationProvider(dataSourceName),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckDataKeycloakSamlClientInstallationProvider(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return fmt.Errorf("resource not found: %s", resourceName)
+		}
+
+		value := rs.Primary.Attributes["value"]
+
+		err := xml.Unmarshal([]byte(value), new(interface{}))
+		if err != nil {
+			return fmt.Errorf("invalid XML: %s\n%s", err, value)
+		}
+
+		return nil
+	}
+}
+
+func testDataSourceKeycloakSamlClientInstallationProvider_basic(realm, clientId string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client" "saml_client" {
+	client_id = "%s"
+	realm_id  = "${keycloak_realm.realm.id}"
+}
+
+data "keycloak_saml_client_installation_provider" "saml_sp_descriptor" {
+  realm_id    = "${keycloak_realm.realm.id}"
+  client_id   = "${keycloak_saml_client.saml_client.id}"
+  provider_id = "saml-sp-descriptor"
+}
+	`, realm, clientId)
+}
diff --git a/provider/resource_keycloak_ldap_role_mapper_test.go b/provider/resource_keycloak_ldap_role_mapper_test.go
new file mode 100644
index 0000000..cdc789b
--- /dev/null
+++ b/provider/resource_keycloak_ldap_role_mapper_test.go
@@ -0,0 +1,561 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"regexp"
+	"testing"
+)
+
+func TestAccKeycloakLdapRoleMapper_basic(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakLdapRoleMapper_basic(realmName, roleMapperName),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+			{
+				ResourceName:      "keycloak_ldap_role_mapper.role_mapper",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getLdapGenericMapperImportId("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.LdapRoleMapper{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakLdapRoleMapper_basic(realmName, roleMapperName),
+				Check:  testAccCheckKeycloakLdapRoleMapperFetch("keycloak_ldap_role_mapper.role_mapper", mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteLdapRoleMapper(mapper.RealmId, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakLdapRoleMapper_basic(realmName, roleMapperName),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_modeValidation(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+	mode := randomStringInSlice(keycloakLdapRoleMapperModes)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "mode", acctest.RandString(10)),
+				ExpectError: regexp.MustCompile("expected mode to be one of .+ got .+"),
+			},
+			{
+				Config: testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "mode", mode),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_membershipAttributeTypeValidation(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+	membershipAttributeType := randomStringInSlice(keycloakLdapRoleMapperMembershipAttributeTypes)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "membership_attribute_type", acctest.RandString(10)),
+				ExpectError: regexp.MustCompile("expected membership_attribute_type to be one of .+ got .+"),
+			},
+			{
+				Config: testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "membership_attribute_type", membershipAttributeType),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_userRolesRetrieveStrategyValidation(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+	userRolesRetrieveStrategy := randomStringInSlice(keycloakLdapRoleMapperUserRolesRetrieveStrategies)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "user_roles_retrieve_strategy", acctest.RandString(10)),
+				ExpectError: regexp.MustCompile("expected user_roles_retrieve_strategy to be one of .+ got .+"),
+			},
+			{
+				Config: testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "user_roles_retrieve_strategy", userRolesRetrieveStrategy),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_rolesLdapFilterValidation(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+	rolesLdapFilter := "(" + acctest.RandString(10) + ")"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "roles_ldap_filter", acctest.RandString(10)),
+				ExpectError: regexp.MustCompile(`validation error: roles ldap filter must start with '\(' and end with '\)'`),
+			},
+			{
+				Config: testKeycloakLdapRoleMapper_basicWithAttrValidation(realmName, roleMapperName, "roles_ldap_filter", rolesLdapFilter),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_updateLdapUserFederationForceNew(t *testing.T) {
+	realmOne := "terraform-" + acctest.RandString(10)
+	realmTwo := "terraform-" + acctest.RandString(10)
+	roleMapperName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakLdapRoleMapper_updateLdapUserFederationBefore(realmOne, realmTwo, roleMapperName),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+			{
+				Config: testKeycloakLdapRoleMapper_updateLdapUserFederationAfter(realmOne, realmTwo, roleMapperName),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakLdapRoleMapper_updateLdapUserFederationInPlace(t *testing.T) {
+	realm := "terraform-" + acctest.RandString(10)
+	clientId := "terraform-" + acctest.RandString(10)
+	useRealmRolesMapping := randomBool()
+
+	roleMapperOne := &keycloak.LdapRoleMapper{
+		Name:                        acctest.RandString(10),
+		RealmId:                     realm,
+		LdapRolesDn:                 acctest.RandString(10),
+		RoleNameLdapAttribute:       acctest.RandString(10),
+		RoleObjectClasses:           []string{acctest.RandString(10), acctest.RandString(10)},
+		MembershipLdapAttribute:     acctest.RandString(10),
+		MembershipAttributeType:     "DN",
+		MembershipUserLdapAttribute: acctest.RandString(10),
+		RolesLdapFilter:             "(" + acctest.RandString(10) + ")",
+		Mode:                        randomStringInSlice(keycloakLdapRoleMapperModes),
+		UserRolesRetrieveStrategy:   randomStringInSlice(keycloakLdapRoleMapperUserRolesRetrieveStrategies),
+		MemberofLdapAttribute:       acctest.RandString(10),
+		UseRealmRolesMapping:        useRealmRolesMapping,
+		ClientId:                    clientId,
+	}
+
+	roleMapperTwo := &keycloak.LdapRoleMapper{
+		Name:                        acctest.RandString(10),
+		RealmId:                     realm,
+		LdapRolesDn:                 acctest.RandString(10),
+		RoleNameLdapAttribute:       acctest.RandString(10),
+		RoleObjectClasses:           []string{acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)},
+		MembershipLdapAttribute:     acctest.RandString(10),
+		MembershipAttributeType:     randomStringInSlice(keycloakLdapRoleMapperMembershipAttributeTypes),
+		MembershipUserLdapAttribute: acctest.RandString(10),
+		RolesLdapFilter:             "(" + acctest.RandString(10) + ")",
+		Mode:                        randomStringInSlice(keycloakLdapRoleMapperModes),
+		UserRolesRetrieveStrategy:   randomStringInSlice(keycloakLdapRoleMapperUserRolesRetrieveStrategies),
+		MemberofLdapAttribute:       acctest.RandString(10),
+		UseRealmRolesMapping:        !useRealmRolesMapping,
+		ClientId:                    clientId,
+	}
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakLdapRoleMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakLdapRoleMapper_basicFromInterface(realm, roleMapperOne),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+			{
+				Config: testKeycloakLdapRoleMapper_basicFromInterface(realm, roleMapperTwo),
+				Check:  testAccCheckKeycloakLdapRoleMapperExists("keycloak_ldap_role_mapper.role_mapper"),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakLdapRoleMapperExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		_, err := getLdapRoleMapperFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakLdapRoleMapperFetch(resourceName string, mapper *keycloak.LdapRoleMapper) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedMapper, err := getLdapRoleMapperFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		mapper.Id = fetchedMapper.Id
+		mapper.RealmId = fetchedMapper.RealmId
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakLdapRoleMapperDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_ldap_role_mapper" {
+				continue
+			}
+
+			id := rs.Primary.ID
+			realm := rs.Primary.Attributes["realm_id"]
+
+			keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+			ldapRoleMapper, _ := keycloakClient.GetLdapRoleMapper(realm, id)
+			if ldapRoleMapper != nil {
+				return fmt.Errorf("ldap role mapper with id %s still exists", id)
+			}
+		}
+
+		return nil
+	}
+}
+
+func getLdapRoleMapperFromState(s *terraform.State, resourceName string) (*keycloak.LdapRoleMapper, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	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"]
+
+	ldapRoleMapper, err := keycloakClient.GetLdapRoleMapper(realm, id)
+	if err != nil {
+		return nil, fmt.Errorf("error getting ldap role mapper with id %s: %s", id, err)
+	}
+
+	return ldapRoleMapper, nil
+}
+
+func testKeycloakLdapRoleMapper_basic(realm, roleMapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_ldap_user_federation" "openldap" {
+	name                    = "openldap"
+	realm_id                = "${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_role_mapper" "role_mapper" {
+	name                    = "%s"
+	realm_id                = "${keycloak_realm.realm.id}"
+	ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}"
+
+	ldap_roles_dn                 = "dc=example,dc=org"
+        role_name_ldap_attribute      = "cn"
+        role_object_classes           = [
+                "group"
+        ]
+        membership_ldap_attribute      = "member"
+        membership_user_ldap_attribute = "sAMAccountName"
+        memberof_ldap_attribute        = "memberOf"
+}
+	`, realm, roleMapperName)
+}
+
+func testKeycloakLdapRoleMapper_basicWithAttrValidation(realm, roleMapperName, attr, val string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_ldap_user_federation" "openldap" {
+	name                    = "openldap"
+	realm_id                = "${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_role_mapper" "role_mapper" {
+	name                        = "%s"
+	realm_id                    = "${keycloak_realm.realm.id}"
+	ldap_user_federation_id     = "${keycloak_ldap_user_federation.openldap.id}"
+
+	%s                          = "%s"
+
+	ldap_roles_dn                 = "dc=example,dc=org"
+	role_name_ldap_attribute      = "cn"
+	role_object_classes           = [
+		"group"
+	]
+	membership_ldap_attribute      = "member"
+	membership_user_ldap_attribute = "sAMAccountName"
+	memberof_ldap_attribute        = "memberOf"
+}
+	`, realm, roleMapperName, attr, val)
+}
+
+func testKeycloakLdapRoleMapper_basicFromInterface(realm string, mapper *keycloak.LdapRoleMapper) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_ldap_user_federation" "openldap" {
+	name                    = "openldap"
+	realm_id                = "${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_role_mapper" "role_mapper" {
+	name                        = "%s"
+	realm_id                    = "${keycloak_realm.realm.id}"
+	ldap_user_federation_id     = "${keycloak_ldap_user_federation.openldap.id}"
+
+	ldap_roles_dn                  = "%s"
+	role_name_ldap_attribute       = "%s"
+	role_object_classes            = %s
+	membership_ldap_attribute      = "%s"
+	membership_attribute_type      = "%s"
+	membership_user_ldap_attribute = "%s"
+	roles_ldap_filter              = "%s"
+	mode                           = "%s"
+	user_roles_retrieve_strategy   = "%s"
+	memberof_ldap_attribute        = "%s"
+	use_realm_roles_mapping        = %t
+	client_id                      = "%s"
+}
+	`, realm, mapper.Name, mapper.LdapRolesDn, mapper.RoleNameLdapAttribute, arrayOfStringsForTerraformResource(mapper.RoleObjectClasses), mapper.MembershipLdapAttribute, mapper.MembershipAttributeType, mapper.MembershipUserLdapAttribute, mapper.RolesLdapFilter, mapper.Mode, mapper.UserRolesRetrieveStrategy, mapper.MemberofLdapAttribute, mapper.UseRealmRolesMapping, mapper.ClientId)
+}
+
+func testKeycloakLdapRoleMapper_updateLdapUserFederationBefore(realmOne, realmTwo, roleMapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm_one" {
+	realm = "%s"
+}
+
+resource "keycloak_realm" "realm_two" {
+	realm = "%s"
+}
+
+resource "keycloak_ldap_user_federation" "openldap_one" {
+	name                    = "openldap"
+	realm_id                = "${keycloak_realm.realm_one.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_user_federation" "openldap_two" {
+	name                    = "openldap"
+	realm_id                = "${keycloak_realm.realm_two.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_role_mapper" "role_mapper" {
+	name                        = "%s"
+	realm_id                    = "${keycloak_realm.realm_one.id}"
+	ldap_user_federation_id     = "${keycloak_ldap_user_federation.openldap_one.id}"
+
+	ldap_roles_dn                 = "dc=example,dc=org"
+	role_name_ldap_attribute      = "cn"
+	role_object_classes           = [
+		"group"
+	]
+	membership_attribute_type      = "DN"
+	membership_ldap_attribute      = "member"
+	membership_user_ldap_attribute = "sAMAccountName"
+	memberof_ldap_attribute        = "memberOf"
+}
+	`, realmOne, realmTwo, roleMapperName)
+}
+
+func testKeycloakLdapRoleMapper_updateLdapUserFederationAfter(realmOne, realmTwo, roleMapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm_one" {
+	realm = "%s"
+}
+
+resource "keycloak_realm" "realm_two" {
+	realm = "%s"
+}
+
+resource "keycloak_ldap_user_federation" "openldap_one" {
+	name                    = "openldap"
+	realm_id                = "${keycloak_realm.realm_one.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_user_federation" "openldap_two" {
+	name                    = "openldap"
+	realm_id                = "${keycloak_realm.realm_two.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_role_mapper" "role_mapper" {
+	name                        = "%s"
+	realm_id                    = "${keycloak_realm.realm_two.id}"
+	ldap_user_federation_id     = "${keycloak_ldap_user_federation.openldap_two.id}"
+
+	ldap_roles_dn                 = "dc=example,dc=org"
+	role_name_ldap_attribute      = "cn"
+	role_object_classes           = [
+		"group"
+	]
+	membership_attribute_type      = "DN"
+	membership_ldap_attribute      = "member"
+	membership_user_ldap_attribute = "sAMAccountName"
+	memberof_ldap_attribute        = "memberOf"
+}
+	`, realmOne, realmTwo, roleMapperName)
+}