Repository: mrparkers/terraform-provider-keycloak
Pull request #320 (2020-06-15 01:51:37)

Improve client scope
I have made the following improvements regarding client scope.

- Added `include_in_token_scope` and `gui_order` arguments into `keycloak_openid_client_scope` resource
- Added `keycloak_saml_client_scope` resource
- Added `keycloak_saml_client_default_scopes` resource

wadahiro: @mrparkers I modified the code and rebased onto the latest master. Could you check it again?

Patch:
diff --git a/docs/resources/keycloak_openid_client_scope.md b/docs/resources/keycloak_openid_client_scope.md
index 81e6026..2172cbd 100644
--- a/docs/resources/keycloak_openid_client_scope.md
+++ b/docs/resources/keycloak_openid_client_scope.md
@@ -16,9 +16,11 @@ resource "keycloak_realm" "realm" {
 }
 
 resource "keycloak_openid_client_scope" "openid_client_scope" {
-    realm_id    = "${keycloak_realm.realm.id}"
-    name        = "groups"
-    description = "When requested, this scope will map a user's group memberships to a claim"
+    realm_id               = "${keycloak_realm.realm.id}"
+    name                   = "groups"
+    description            = "When requested, this scope will map a user's group memberships to a claim"
+    include_in_token_scope = true
+    gui_order              = 1
 }
 ```
 
@@ -32,6 +34,8 @@ The following arguments are supported:
 - `consent_screen_text` - (Optional) When set, a consent screen will be displayed to users
 authenticating to clients with this scope attached. The consent screen will display the string
 value of this attribute.
+- `include_in_token_scope` - (Optional) When `true`, the name of this client scope will be added to the access token property 'scope' as well as to the Token Introspection Endpoint response.
+- `gui_order` - (Optional) Specify order of the client scope in GUI (such as in Consent page) as integer.
 
 ### Import
 
diff --git a/docs/resources/keycloak_saml_client_default_scopes.md b/docs/resources/keycloak_saml_client_default_scopes.md
new file mode 100644
index 0000000..b99449d
--- /dev/null
+++ b/docs/resources/keycloak_saml_client_default_scopes.md
@@ -0,0 +1,68 @@
+# keycloak_saml_client_default_scopes
+
+Allows for managing a Keycloak client's default client scopes. A default
+scope that is attached to a client using the SAML protocol will
+automatically use the protocol mappers defined within that scope to build
+claims for this client.
+
+Note that this resource attempts to be an **authoritative** source over
+default scopes for a Keycloak client using the SAML protocol.
+This means that once Terraform controls a particular client's default scopes,
+it will attempt to remove any default scopes that were attached manually,
+and it will attempt to add any default scopes that were detached manually.
+
+By default, Keycloak sets the `role_list` scope as default scope for every
+newly created client. If you create this resource for the first time and
+do not include this scope, a following run of `terraform plan` will result
+in changes.
+
+### Example Usage
+
+```hcl
+resource "keycloak_realm" "realm" {
+    realm   = "my-realm"
+    enabled = true
+}
+
+resource "keycloak_saml_client" "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")}"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+    realm_id = "${keycloak_realm.realm.id}"
+    name     = "test-client-scope"
+}
+
+resource "keycloak_saml_client_default_scopes" "client_default_scopes" {
+    realm_id       = "${keycloak_realm.realm.id}"
+    client_id      = "${keycloak_saml_client.client.id}"
+
+    default_scopes = [
+        "role_list",
+        "${keycloak_saml_client_scope.client_scope.name}"
+    ]
+}
+
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this client and scopes exists in.
+- `client_id` - (Required) The ID of the client to attach default scopes to. Note that this is the unique ID of the client generated by Keycloak.
+- `default_scopes` - (Required) An array of client scope names to attach to this client.
+
+### Import
+
+This resource does not support import. Instead of importing, feel free to create this resource
+as if it did not already exist on the server.
diff --git a/docs/resources/keycloak_saml_client_scope.md b/docs/resources/keycloak_saml_client_scope.md
new file mode 100644
index 0000000..cb8e546
--- /dev/null
+++ b/docs/resources/keycloak_saml_client_scope.md
@@ -0,0 +1,46 @@
+# keycloak_saml_client_scope
+
+Allows for creating and managing Keycloak client scopes that can be attached to
+clients that use the SAML protocol.
+
+Client Scopes can be used to share common protocol and role mappings between multiple
+clients within a realm.
+
+### Example Usage
+
+```hcl
+resource "keycloak_realm" "realm" {
+    realm   = "my-realm"
+    enabled = true
+}
+
+resource "keycloak_saml_client_scope" "saml_client_scope" {
+    realm_id    = "${keycloak_realm.realm.id}"
+    name        = "groups"
+    description = "This scope will map a user's group memberships to SAML assertion"
+    gui_order   = 1
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this client scope belongs to.
+- `name` - (Required) The display name of this client scope in the GUI.
+- `description` - (Optional) The description of this client scope in the GUI.
+- `consent_screen_text` - (Optional) When set, a consent screen will be displayed to users
+authenticating to clients with this scope attached. The consent screen will display the string
+value of this attribute.
+- `gui_order` - (Optional) Specify order of the client scope in GUI (such as in Consent page) as integer.
+
+### Import
+
+Client scopes can be imported using the format `{{realm_id}}/{{client_scope_id}}`, where `client_scope_id` is the unique ID that Keycloak
+assigns to the client scope upon creation. This value can be found in the URI when editing this client scope in the GUI, and is typically a GUID.
+
+Example:
+
+```bash
+$ terraform import keycloak_saml_client_scope.saml_client_scope my-realm/e8a5d115-6985-4de3-a0f5-732e1be4525e
+```
diff --git a/example/main.tf b/example/main.tf
index b21b30e..fdad833 100644
--- a/example/main.tf
+++ b/example/main.tf
@@ -567,6 +567,11 @@ resource keycloak_attribute_importer_identity_provider_mapper oidc {
   claim_name              = "upn"
   identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
   user_attribute          = "email"
+  
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }
 }
 
 resource keycloak_attribute_to_role_identity_provider_mapper oidc {
@@ -576,6 +581,11 @@ resource keycloak_attribute_to_role_identity_provider_mapper oidc {
   identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
   claim_value             = "value"
   role                    = "testRole"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 resource keycloak_user_template_importer_identity_provider_mapper oidc {
@@ -583,6 +593,11 @@ resource keycloak_user_template_importer_identity_provider_mapper oidc {
   name                    = "userTemplate"
   identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
   template                = "$${ALIAS}/$${CLAIM.upn}"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 resource keycloak_hardcoded_role_identity_provider_mapper oidc {
@@ -590,6 +605,11 @@ resource keycloak_hardcoded_role_identity_provider_mapper oidc {
   name                    = "hardcodedRole"
   identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
   role                    = "testrole"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 resource keycloak_hardcoded_attribute_identity_provider_mapper oidc {
@@ -599,6 +619,11 @@ resource keycloak_hardcoded_attribute_identity_provider_mapper oidc {
   attribute_name          = "attribute"
   attribute_value         = "value"
   user_session            = true
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 resource keycloak_saml_identity_provider saml {
@@ -613,6 +638,11 @@ resource keycloak_attribute_importer_identity_provider_mapper saml {
   attribute_name          = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
   identity_provider_alias = keycloak_saml_identity_provider.saml.alias
   user_attribute          = "email"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }
 }
 
 resource keycloak_attribute_to_role_identity_provider_mapper saml {
@@ -622,6 +652,11 @@ resource keycloak_attribute_to_role_identity_provider_mapper saml {
   identity_provider_alias = keycloak_saml_identity_provider.saml.alias
   attribute_value         = "value"
   role                    = "testRole"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }
 }
 
 resource keycloak_user_template_importer_identity_provider_mapper saml {
@@ -629,6 +664,11 @@ resource keycloak_user_template_importer_identity_provider_mapper saml {
   name                    = "userTemplate"
   identity_provider_alias = keycloak_saml_identity_provider.saml.alias
   template                = "$${ALIAS}/$${NAMEID}"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 resource keycloak_hardcoded_role_identity_provider_mapper saml {
@@ -636,6 +676,11 @@ resource keycloak_hardcoded_role_identity_provider_mapper saml {
   name                    = "hardcodedRole"
   identity_provider_alias = keycloak_saml_identity_provider.saml.alias
   role                    = "testrole"
+
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 resource keycloak_hardcoded_attribute_identity_provider_mapper saml {
@@ -645,6 +690,11 @@ resource keycloak_hardcoded_attribute_identity_provider_mapper saml {
   attribute_name          = "attribute"
   attribute_value         = "value"
   user_session            = false
+  
+  #KC10 support
+  extra_config = {
+    syncMode = "INHERIT"    
+  }  
 }
 
 data "keycloak_openid_client" "broker" {
diff --git a/example/roles.tf b/example/roles.tf
index 0722f7f..fffb1ab 100644
--- a/example/roles.tf
+++ b/example/roles.tf
@@ -160,6 +160,44 @@ resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_admin_role_mappi
   role_id   = keycloak_role.pet_api_admin.id
 }
 
+// Realm roles
+
+resource "keycloak_role" "realm_reader" {  
+  realm_id  = keycloak_realm.roles_example.id
+  name        = "realm_reader"
+  description = "Reader realm role"
+}
+
+resource "keycloak_role" "realm_writer" {
+  realm_id  = keycloak_realm.roles_example.id
+  name        = "realm_writer"
+  description = "Writer realm role"
+}
+
+resource "keycloak_role" "realm_admin" {
+  realm_id  = keycloak_realm.roles_example.id
+  name        = "realm_admin"
+  description = "Admin realm composite role"
+  composite_roles = [
+    keycloak_role.realm_reader.id,
+    keycloak_role.realm_writer.id
+  ]
+}
+
+// Client scope for realm roles mapping 
+
+resource "keycloak_openid_client_scope" "petstore_api_access_scope" {
+  realm_id  = keycloak_realm.roles_example.id
+  name        = "petstore-api-access"
+  description = "Optional scope offering additional information for petstore api access"
+}
+
+resource "keycloak_generic_client_role_mapper" "petstore_api_access_scope_admin" {
+    realm_id  = keycloak_realm.roles_example.id
+    client_scope_id =  keycloak_openid_client_scope.petstore_api_access_scope.id
+    role_id         = keycloak_role.realm_admin.id
+}
+
 // Users and groups
 
 resource "keycloak_group" "pet_api_base" {
diff --git a/keycloak/identity_provider_mapper.go b/keycloak/identity_provider_mapper.go
index f30927a..eb98695 100644
--- a/keycloak/identity_provider_mapper.go
+++ b/keycloak/identity_provider_mapper.go
@@ -1,20 +1,25 @@
 package keycloak
 
 import (
+	"encoding/json"
 	"fmt"
 	"log"
+	"reflect"
+	"strconv"
+	"strings"
 )
 
 type IdentityProviderMapperConfig struct {
-	UserAttribute         string `json:"user.attribute,omitempty"`
-	Claim                 string `json:"claim,omitempty"`
-	ClaimValue            string `json:"claim.value,omitempty"`
-	HardcodedAttribute    string `json:"attribute,omitempty"`
-	Attribute             string `json:"attribute.name,omitempty"`
-	AttributeValue        string `json:"attribute.value,omitempty"`
-	AttributeFriendlyName string `json:"attribute.friendly.name,omitempty"`
-	Template              string `json:"template,omitempty"`
-	Role                  string `json:"role,omitempty"`
+	UserAttribute         string                 `json:"user.attribute,omitempty"`
+	Claim                 string                 `json:"claim,omitempty"`
+	ClaimValue            string                 `json:"claim.value,omitempty"`
+	HardcodedAttribute    string                 `json:"attribute,omitempty"`
+	Attribute             string                 `json:"attribute.name,omitempty"`
+	AttributeValue        string                 `json:"attribute.value,omitempty"`
+	AttributeFriendlyName string                 `json:"attribute.friendly.name,omitempty"`
+	Template              string                 `json:"template,omitempty"`
+	Role                  string                 `json:"role,omitempty"`
+	ExtraConfig           map[string]interface{} `json:"-"`
 }
 
 type IdentityProviderMapper struct {
@@ -59,3 +64,57 @@ func (keycloakClient *KeycloakClient) UpdateIdentityProviderMapper(identityProvi
 func (keycloakClient *KeycloakClient) DeleteIdentityProviderMapper(realm, alias, id string) error {
 	return keycloakClient.delete(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/mappers/%s", realm, alias, id), nil)
 }
+
+func (f *IdentityProviderMapperConfig) UnmarshalJSON(data []byte) error {
+	f.ExtraConfig = map[string]interface{}{}
+	err := json.Unmarshal(data, &f.ExtraConfig)
+	if err != nil {
+		return err
+	}
+	v := reflect.ValueOf(f).Elem()
+	for i := 0; i < v.NumField(); i++ {
+		structField := v.Type().Field(i)
+		jsonKey := strings.Split(structField.Tag.Get("json"), ",")[0]
+		if jsonKey != "-" {
+			value, ok := f.ExtraConfig[jsonKey]
+			if ok {
+				field := v.FieldByName(structField.Name)
+				if field.IsValid() && field.CanSet() {
+					if field.Kind() == reflect.String {
+						field.SetString(value.(string))
+					} else if field.Kind() == reflect.Bool {
+						boolVal, err := strconv.ParseBool(value.(string))
+						if err == nil {
+							field.Set(reflect.ValueOf(KeycloakBoolQuoted(boolVal)))
+						}
+					}
+					delete(f.ExtraConfig, jsonKey)
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *IdentityProviderMapperConfig) MarshalJSON() ([]byte, error) {
+	out := map[string]interface{}{}
+
+	for k, v := range f.ExtraConfig {
+		out[k] = v
+	}
+	v := reflect.ValueOf(f).Elem()
+	for i := 0; i < v.NumField(); i++ {
+		jsonKey := strings.Split(v.Type().Field(i).Tag.Get("json"), ",")[0]
+		if jsonKey != "-" {
+			field := v.Field(i)
+			if field.IsValid() && field.CanSet() {
+				if field.Kind() == reflect.String {
+					out[jsonKey] = field.String()
+				} else if field.Kind() == reflect.Bool {
+					out[jsonKey] = KeycloakBoolQuoted(field.Bool())
+				}
+			}
+		}
+	}
+	return json.Marshal(out)
+}
diff --git a/keycloak/openid_client_scope.go b/keycloak/openid_client_scope.go
index 9c7bd9e..8528935 100644
--- a/keycloak/openid_client_scope.go
+++ b/keycloak/openid_client_scope.go
@@ -11,8 +11,10 @@ type OpenidClientScope struct {
 	Description string `json:"description"`
 	Protocol    string `json:"protocol"`
 	Attributes  struct {
-		DisplayOnConsentScreen string `json:"display.on.consent.screen"` // boolean in string form
-		ConsentScreenText      string `json:"consent.screen.text"`
+		DisplayOnConsentScreen KeycloakBoolQuoted `json:"display.on.consent.screen"` // boolean in string form
+		ConsentScreenText      string             `json:"consent.screen.text"`
+		GuiOrder               string             `json:"gui.order"`
+		IncludeInTokenScope    KeycloakBoolQuoted `json:"include.in.token.scope"` // boolean in string form
 	} `json:"attributes"`
 }
 
diff --git a/keycloak/role_scope_mapping.go b/keycloak/role_scope_mapping.go
index 4aabd4f..35506d4 100644
--- a/keycloak/role_scope_mapping.go
+++ b/keycloak/role_scope_mapping.go
@@ -5,10 +5,19 @@ import (
 )
 
 func roleScopeMappingUrl(realmId, clientId string, clientScopeId string, role *Role) string {
+
 	if clientId != "" {
-		return fmt.Sprintf("/realms/%s/clients/%s/scope-mappings/clients/%s", realmId, clientId, role.ClientId)
-	} else {
+		if role.ClientRole {
+			return fmt.Sprintf("/realms/%s/clients/%s/scope-mappings/clients/%s", realmId, clientId, role.ClientId)
+		} else {
+			return fmt.Sprintf("/realms/%s/clients/%s/scope-mappings/realm", realmId, clientId)
+		}
+	}
+
+	if role.ClientRole {
 		return fmt.Sprintf("/realms/%s/client-scopes/%s/scope-mappings/clients/%s", realmId, clientScopeId, role.ClientId)
+	} else {
+		return fmt.Sprintf("/realms/%s/client-scopes/%s/scope-mappings/realm", realmId, clientScopeId)
 	}
 }
 
diff --git a/keycloak/saml_client.go b/keycloak/saml_client.go
index 4e3b848..51bb7d5 100644
--- a/keycloak/saml_client.go
+++ b/keycloak/saml_client.go
@@ -78,6 +78,29 @@ func (keycloakClient *KeycloakClient) GetSamlClientInstallationProvider(realmId,
 	return value, err
 }
 
+func (keycloakClient *KeycloakClient) GetSamlClientByClientId(realmId, clientId string) (*SamlClient, error) {
+	var clients []SamlClient
+
+	params := map[string]string{
+		"clientId": clientId,
+	}
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients", realmId), &clients, params)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(clients) == 0 {
+		return nil, fmt.Errorf("saml client with name %s does not exist", clientId)
+	}
+
+	client := clients[0]
+
+	client.RealmId = realmId
+
+	return &client, nil
+}
+
 func (keycloakClient *KeycloakClient) UpdateSamlClient(client *SamlClient) error {
 	client.Protocol = "saml"
 	client.ClientAuthenticatorType = "client-secret"
@@ -88,3 +111,65 @@ func (keycloakClient *KeycloakClient) UpdateSamlClient(client *SamlClient) error
 func (keycloakClient *KeycloakClient) DeleteSamlClient(realmId, id string) error {
 	return keycloakClient.delete(fmt.Sprintf("/realms/%s/clients/%s", realmId, id), nil)
 }
+
+func (keycloakClient *KeycloakClient) getSamlClientScopes(realmId, clientId, t string) ([]*SamlClientScope, error) {
+	var scopes []*SamlClientScope
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients/%s/%s-client-scopes", realmId, clientId, t), &scopes, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return scopes, nil
+}
+
+func (keycloakClient *KeycloakClient) GetSamlClientDefaultScopes(realmId, clientId string) ([]*SamlClientScope, error) {
+	return keycloakClient.getSamlClientScopes(realmId, clientId, "default")
+}
+
+func (keycloakClient *KeycloakClient) attachSamlClientScopes(realmId, clientId, t string, scopeNames []string) error {
+	_, err := keycloakClient.GetSamlClient(realmId, clientId)
+	if err != nil && ErrorIs404(err) {
+		return fmt.Errorf("validation error: client with id %s does not exist", clientId)
+	} else if err != nil {
+		return err
+	}
+
+	allSamlClientScopes, err := keycloakClient.ListSamlClientScopesWithFilter(realmId, includeSamlClientScopesMatchingNames(scopeNames))
+	if err != nil {
+		return err
+	}
+
+	for _, samlClientScope := range allSamlClientScopes {
+		err := keycloakClient.put(fmt.Sprintf("/realms/%s/clients/%s/%s-client-scopes/%s", realmId, clientId, t, samlClientScope.Id), nil)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) AttachSamlClientDefaultScopes(realmId, clientId string, scopeNames []string) error {
+	return keycloakClient.attachSamlClientScopes(realmId, clientId, "default", scopeNames)
+}
+
+func (keycloakClient *KeycloakClient) detachSamlClientScopes(realmId, clientId, t string, scopeNames []string) error {
+	allSamlClientScopes, err := keycloakClient.ListSamlClientScopesWithFilter(realmId, includeSamlClientScopesMatchingNames(scopeNames))
+	if err != nil {
+		return err
+	}
+
+	for _, samlClientScope := range allSamlClientScopes {
+		err := keycloakClient.delete(fmt.Sprintf("/realms/%s/clients/%s/%s-client-scopes/%s", realmId, clientId, t, samlClientScope.Id), nil)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) DetachSamlClientDefaultScopes(realmId, clientId string, scopeNames []string) error {
+	return keycloakClient.detachSamlClientScopes(realmId, clientId, "default", scopeNames)
+}
diff --git a/keycloak/saml_client_scope.go b/keycloak/saml_client_scope.go
new file mode 100644
index 0000000..b06ceb8
--- /dev/null
+++ b/keycloak/saml_client_scope.go
@@ -0,0 +1,104 @@
+package keycloak
+
+import (
+	"fmt"
+)
+
+type SamlClientScope struct {
+	Id          string `json:"id,omitempty"`
+	RealmId     string `json:"-"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Protocol    string `json:"protocol"`
+	Attributes  struct {
+		DisplayOnConsentScreen KeycloakBoolQuoted `json:"display.on.consent.screen"` // boolean in string form
+		ConsentScreenText      string             `json:"consent.screen.text"`
+		GuiOrder               string             `json:"gui.order"`
+	} `json:"attributes"`
+}
+
+type SamlClientScopeFilterFunc func(*SamlClientScope) bool
+
+func (keycloakClient *KeycloakClient) NewSamlClientScope(clientScope *SamlClientScope) error {
+	clientScope.Protocol = "saml"
+
+	_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/client-scopes", clientScope.RealmId), clientScope)
+	if err != nil {
+		return err
+	}
+
+	clientScope.Id = getIdFromLocationHeader(location)
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) GetSamlClientScope(realmId, id string) (*SamlClientScope, error) {
+	var clientScope SamlClientScope
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/client-scopes/%s", realmId, id), &clientScope, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	clientScope.RealmId = realmId
+
+	return &clientScope, nil
+}
+
+func (keycloakClient *KeycloakClient) GetSamlDefaultClientScopes(realmId, clientId string) (*[]SamlClientScope, error) {
+	var clientScopes []SamlClientScope
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients/%s/default-client-scopes", realmId, clientId), &clientScopes, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, clientScope := range clientScopes {
+		clientScope.RealmId = realmId
+	}
+
+	return &clientScopes, nil
+}
+
+func (keycloakClient *KeycloakClient) UpdateSamlClientScope(clientScope *SamlClientScope) error {
+	clientScope.Protocol = "saml"
+
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/client-scopes/%s", clientScope.RealmId, clientScope.Id), clientScope)
+}
+
+func (keycloakClient *KeycloakClient) DeleteSamlClientScope(realmId, id string) error {
+	return keycloakClient.delete(fmt.Sprintf("/realms/%s/client-scopes/%s", realmId, id), nil)
+}
+
+func (keycloakClient *KeycloakClient) ListSamlClientScopesWithFilter(realmId string, filter SamlClientScopeFilterFunc) ([]*SamlClientScope, error) {
+	var clientScopes []SamlClientScope
+	var samlClientScopes []*SamlClientScope
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/client-scopes", realmId), &clientScopes, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, clientScope := range clientScopes {
+		if clientScope.Protocol == "saml" && filter(&clientScope) {
+			scope := new(SamlClientScope)
+			*scope = clientScope
+
+			samlClientScopes = append(samlClientScopes, scope)
+		}
+	}
+
+	return samlClientScopes, nil
+}
+
+func includeSamlClientScopesMatchingNames(scopeNames []string) SamlClientScopeFilterFunc {
+	return func(scope *SamlClientScope) bool {
+		for _, scopeName := range scopeNames {
+			if scopeName == scope.Name {
+				return true
+			}
+		}
+
+		return false
+	}
+}
diff --git a/provider/generic_keycloak_identity_provider_mapper.go b/provider/generic_keycloak_identity_provider_mapper.go
index 83109ec..b4db8f9 100644
--- a/provider/generic_keycloak_identity_provider_mapper.go
+++ b/provider/generic_keycloak_identity_provider_mapper.go
@@ -2,9 +2,10 @@ package provider
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"strings"
 )
 
 type identityProviderMapperDataGetterFunc func(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error)
@@ -35,6 +36,10 @@ func resourceKeycloakIdentityProviderMapper() *schema.Resource {
 				ForceNew:    true,
 				Description: "IDP Alias",
 			},
+			"extra_config": {
+				Type:     schema.TypeMap,
+				Optional: true,
+			},
 		},
 	}
 }
diff --git a/provider/provider.go b/provider/provider.go
index b06a1d6..145f3ba 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -53,6 +53,8 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_openid_client_default_scopes":                      resourceKeycloakOpenidClientDefaultScopes(),
 			"keycloak_openid_client_optional_scopes":                     resourceKeycloakOpenidClientOptionalScopes(),
 			"keycloak_saml_client":                                       resourceKeycloakSamlClient(),
+			"keycloak_saml_client_scope":                                 resourceKeycloakSamlClientScope(),
+			"keycloak_saml_client_default_scopes":                        resourceKeycloakSamlClientDefaultScopes(),
 			"keycloak_generic_client_protocol_mapper":                    resourceKeycloakGenericClientProtocolMapper(),
 			"keycloak_generic_client_role_mapper":                        resourceKeycloakGenericClientRoleMapper(),
 			"keycloak_saml_user_attribute_protocol_mapper":               resourceKeycloakSamlUserAttributeProtocolMapper(),
diff --git a/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go b/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go
index 906c8a5..f10f284 100644
--- a/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go
+++ b/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go
@@ -2,6 +2,7 @@ package provider
 
 import (
 	"fmt"
+
 	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
 )
@@ -42,6 +43,12 @@ func resourceKeycloakAttributeImporterIdentityProviderMapper() *schema.Resource
 func getAttributeImporterIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) {
 	keycloakClient := meta.(*keycloak.KeycloakClient)
 	rec, _ := getIdentityProviderMapperFromData(data)
+	extraConfig := map[string]interface{}{}
+	if v, ok := data.GetOk("extra_config"); ok {
+		for key, value := range v.(map[string]interface{}) {
+			extraConfig[key] = value
+		}
+	}
 	identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias)
 	if err != nil {
 		return nil, handleNotFoundError(err, data)
@@ -49,6 +56,7 @@ func getAttributeImporterIdentityProviderMapperFromData(data *schema.ResourceDat
 	rec.IdentityProviderMapper = fmt.Sprintf("%s-user-attribute-idp-mapper", identityProvider.ProviderId)
 	rec.Config = &keycloak.IdentityProviderMapperConfig{
 		UserAttribute: data.Get("user_attribute").(string),
+		ExtraConfig:   extraConfig,
 	}
 	if identityProvider.ProviderId == "saml" {
 		if attr, ok := data.GetOk("attribute_friendly_name"); ok {
@@ -75,5 +83,6 @@ func setAttributeImporterIdentityProviderMapperData(data *schema.ResourceData, i
 	data.Set("user_attribute", identityProviderMapper.Config.UserAttribute)
 	data.Set("attribute_friendly_name", identityProviderMapper.Config.AttributeFriendlyName)
 	data.Set("claim_name", identityProviderMapper.Config.Claim)
+	data.Set("extra_config", identityProviderMapper.Config.ExtraConfig)
 	return nil
 }
diff --git a/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go b/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go
index aae4794..7869bff 100644
--- a/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go
+++ b/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go
@@ -2,6 +2,7 @@ package provider
 
 import (
 	"fmt"
+
 	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
 )
@@ -52,13 +53,20 @@ func resourceKeycloakAttributeToRoleIdentityProviderMapper() *schema.Resource {
 func getAttributeToRoleIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) {
 	keycloakClient := meta.(*keycloak.KeycloakClient)
 	rec, _ := getIdentityProviderMapperFromData(data)
+	extraConfig := map[string]interface{}{}
+	if v, ok := data.GetOk("extra_config"); ok {
+		for key, value := range v.(map[string]interface{}) {
+			extraConfig[key] = value
+		}
+	}
 	identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias)
 	if err != nil {
 		return nil, handleNotFoundError(err, data)
 	}
 	rec.IdentityProviderMapper = fmt.Sprintf("%s-role-idp-mapper", identityProvider.ProviderId)
 	rec.Config = &keycloak.IdentityProviderMapperConfig{
-		Role: data.Get("role").(string),
+		Role:        data.Get("role").(string),
+		ExtraConfig: extraConfig,
 	}
 	if identityProvider.ProviderId == "saml" {
 		if attr, ok := data.GetOk("attribute_friendly_name"); ok {
@@ -95,5 +103,6 @@ func setAttributeToRoleIdentityProviderMapperData(data *schema.ResourceData, ide
 	data.Set("claim_name", identityProviderMapper.Config.Claim)
 	data.Set("claim_value", identityProviderMapper.Config.ClaimValue)
 	data.Set("attribute_friendly_name", identityProviderMapper.Config.AttributeFriendlyName)
+	data.Set("extra_config", identityProviderMapper.Config.ExtraConfig)
 	return nil
 }
diff --git a/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go b/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go
index ce53310..4cf1923 100644
--- a/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go
+++ b/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go
@@ -2,6 +2,7 @@ package provider
 
 import (
 	"fmt"
+
 	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
 )
@@ -35,10 +36,17 @@ func resourceKeycloakHardcodedAttributeIdentityProviderMapper() *schema.Resource
 
 func getHardcodedAttributeIdentityProviderMapperFromData(data *schema.ResourceData, _ interface{}) (*keycloak.IdentityProviderMapper, error) {
 	rec, _ := getIdentityProviderMapperFromData(data)
+	extraConfig := map[string]interface{}{}
+	if v, ok := data.GetOk("extra_config"); ok {
+		for key, value := range v.(map[string]interface{}) {
+			extraConfig[key] = value
+		}
+	}
 	rec.IdentityProviderMapper = getHardcodedAttributeIdentityProviderMapperType(data.Get("user_session").(bool))
 	rec.Config = &keycloak.IdentityProviderMapperConfig{
 		HardcodedAttribute: data.Get("attribute_name").(string),
 		AttributeValue:     data.Get("attribute_value").(string),
+		ExtraConfig:        extraConfig,
 	}
 	return rec, nil
 }
@@ -47,6 +55,7 @@ func setHardcodedAttributeIdentityProviderMapperData(data *schema.ResourceData,
 	setIdentityProviderMapperData(data, identityProviderMapper)
 	data.Set("attribute_name", identityProviderMapper.Config.HardcodedAttribute)
 	data.Set("attribute_value", identityProviderMapper.Config.AttributeValue)
+	data.Set("extra_config", identityProviderMapper.Config.ExtraConfig)
 	mapperType, err := getUserSessionFromHardcodedAttributeIdentityProviderMapperType(identityProviderMapper.IdentityProviderMapper)
 	if err != nil {
 		return err
diff --git a/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go b/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go
index 85ab5ec..a20edc6 100644
--- a/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go
+++ b/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go
@@ -23,9 +23,16 @@ func resourceKeycloakHardcodedRoleIdentityProviderMapper() *schema.Resource {
 
 func getHardcodedRoleIdentityProviderMapperFromData(data *schema.ResourceData, _ interface{}) (*keycloak.IdentityProviderMapper, error) {
 	rec, _ := getIdentityProviderMapperFromData(data)
+	extraConfig := map[string]interface{}{}
+	if v, ok := data.GetOk("extra_config"); ok {
+		for key, value := range v.(map[string]interface{}) {
+			extraConfig[key] = value
+		}
+	}
 	rec.IdentityProviderMapper = "oidc-hardcoded-role-idp-mapper"
 	rec.Config = &keycloak.IdentityProviderMapperConfig{
-		Role: data.Get("role").(string),
+		Role:        data.Get("role").(string),
+		ExtraConfig: extraConfig,
 	}
 	return rec, nil
 }
@@ -33,5 +40,6 @@ func getHardcodedRoleIdentityProviderMapperFromData(data *schema.ResourceData, _
 func setHardcodedRoleIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error {
 	setIdentityProviderMapperData(data, identityProviderMapper)
 	data.Set("role", identityProviderMapper.Config.Role)
+	data.Set("extra_config", identityProviderMapper.Config.ExtraConfig)
 	return nil
 }
diff --git a/provider/resource_keycloak_openid_client_scope.go b/provider/resource_keycloak_openid_client_scope.go
index 6165530..37d17a0 100644
--- a/provider/resource_keycloak_openid_client_scope.go
+++ b/provider/resource_keycloak_openid_client_scope.go
@@ -2,9 +2,11 @@ package provider
 
 import (
 	"fmt"
+	"strconv"
+	"strings"
+
 	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"strings"
 )
 
 func resourceKeycloakOpenidClientScope() *schema.Resource {
@@ -35,6 +37,15 @@ func resourceKeycloakOpenidClientScope() *schema.Resource {
 				Type:     schema.TypeString,
 				Optional: true,
 			},
+			"include_in_token_scope": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  true,
+			},
+			"gui_order": {
+				Type:     schema.TypeInt,
+				Optional: true,
+			},
 		},
 	}
 }
@@ -49,9 +60,16 @@ func getClientScopeFromData(data *schema.ResourceData) *keycloak.OpenidClientSco
 
 	if consentScreenText, ok := data.GetOk("consent_screen_text"); ok {
 		clientScope.Attributes.ConsentScreenText = consentScreenText.(string)
-		clientScope.Attributes.DisplayOnConsentScreen = "true"
+		clientScope.Attributes.DisplayOnConsentScreen = true
 	} else {
-		clientScope.Attributes.DisplayOnConsentScreen = "false"
+		clientScope.Attributes.DisplayOnConsentScreen = false
+	}
+
+	clientScope.Attributes.IncludeInTokenScope = keycloak.KeycloakBoolQuoted(data.Get("include_in_token_scope").(bool))
+
+	// Treat 0 as an empty string for the purpose of omitting the attribute to reset the order
+	if guiOrder := data.Get("gui_order").(int); guiOrder != 0 {
+		clientScope.Attributes.GuiOrder = strconv.Itoa(guiOrder)
 	}
 
 	return clientScope
@@ -64,9 +82,14 @@ func setClientScopeData(data *schema.ResourceData, clientScope *keycloak.OpenidC
 	data.Set("name", clientScope.Name)
 	data.Set("description", clientScope.Description)
 
-	if clientScope.Attributes.DisplayOnConsentScreen == "true" {
+	if clientScope.Attributes.DisplayOnConsentScreen {
 		data.Set("consent_screen_text", clientScope.Attributes.ConsentScreenText)
 	}
+
+	data.Set("include_in_token_scope", clientScope.Attributes.IncludeInTokenScope)
+	if guiOrder, err := strconv.Atoi(clientScope.Attributes.GuiOrder); err == nil {
+		data.Set("gui_order", guiOrder)
+	}
 }
 
 func resourceKeycloakOpenidClientScopeCreate(data *schema.ResourceData, meta interface{}) error {
diff --git a/provider/resource_keycloak_saml_client_default_scopes.go b/provider/resource_keycloak_saml_client_default_scopes.go
new file mode 100644
index 0000000..31a39cb
--- /dev/null
+++ b/provider/resource_keycloak_saml_client_default_scopes.go
@@ -0,0 +1,128 @@
+package provider
+
+import (
+	"fmt"
+
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func resourceKeycloakSamlClientDefaultScopes() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakSamlClientDefaultScopesCreate,
+		Read:   resourceKeycloakSamlClientDefaultScopesRead,
+		Delete: resourceKeycloakSamlClientDefaultScopesDelete,
+		Update: resourceKeycloakSamlClientDefaultScopesUpdate,
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"client_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"default_scopes": {
+				Type:     schema.TypeSet,
+				Elem:     &schema.Schema{Type: schema.TypeString},
+				Required: true,
+				Set:      schema.HashString,
+			},
+		},
+	}
+}
+
+func resourceKeycloakSamlClientDefaultScopesCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	defaultScopes := data.Get("default_scopes").(*schema.Set)
+
+	err := keycloakClient.AttachSamlClientDefaultScopes(realmId, clientId, interfaceSliceToStringSlice(defaultScopes.List()))
+	if err != nil {
+		return err
+	}
+
+	data.SetId(samlClientDefaultScopesId(realmId, clientId))
+
+	return resourceKeycloakSamlClientDefaultScopesRead(data, meta)
+}
+
+func samlClientDefaultScopesId(realmId string, clientId string) string {
+	return fmt.Sprintf("%s/%s", realmId, clientId)
+}
+
+func resourceKeycloakSamlClientDefaultScopesRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+
+	clientScopes, err := keycloakClient.GetSamlClientDefaultScopes(realmId, clientId)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	var defaultScopes []string
+	for _, clientScope := range clientScopes {
+		defaultScopes = append(defaultScopes, clientScope.Name)
+	}
+
+	data.Set("default_scopes", defaultScopes)
+	data.SetId(samlClientDefaultScopesId(realmId, clientId))
+
+	return nil
+}
+
+func resourceKeycloakSamlClientDefaultScopesUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	tfSamlClientDefaultScopes := data.Get("default_scopes").(*schema.Set)
+
+	keycloakSamlClientDefaultScopes, err := keycloakClient.GetSamlClientDefaultScopes(realmId, clientId)
+	if err != nil {
+		return err
+	}
+
+	var samlClientDefaultScopesToDetach []string
+	for _, keycloakSamlClientDefaultScope := range keycloakSamlClientDefaultScopes {
+		// if this scope is attached in keycloak and tf state, no update is required
+		// remove it from the set so we can look at scopes that need to be attached later
+		if tfSamlClientDefaultScopes.Contains(keycloakSamlClientDefaultScope.Name) {
+			tfSamlClientDefaultScopes.Remove(keycloakSamlClientDefaultScope.Name)
+		} else {
+			// if this scope is attached in keycloak but not in tf state, add them to a slice containing all scopes to detach
+			samlClientDefaultScopesToDetach = append(samlClientDefaultScopesToDetach, keycloakSamlClientDefaultScope.Name)
+		}
+	}
+
+	// detach scopes that aren't in tf state
+	err = keycloakClient.DetachSamlClientDefaultScopes(realmId, clientId, samlClientDefaultScopesToDetach)
+	if err != nil {
+		return err
+	}
+
+	// attach scopes that exist in tf state but not in keycloak
+	err = keycloakClient.AttachSamlClientDefaultScopes(realmId, clientId, interfaceSliceToStringSlice(tfSamlClientDefaultScopes.List()))
+	if err != nil {
+		return err
+	}
+
+	data.SetId(samlClientDefaultScopesId(realmId, clientId))
+
+	return resourceKeycloakSamlClientDefaultScopesRead(data, meta)
+}
+
+func resourceKeycloakSamlClientDefaultScopesDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	defaultScopes := data.Get("default_scopes").(*schema.Set)
+
+	return keycloakClient.DetachSamlClientDefaultScopes(realmId, clientId, interfaceSliceToStringSlice(defaultScopes.List()))
+}
diff --git a/provider/resource_keycloak_saml_client_scope.go b/provider/resource_keycloak_saml_client_scope.go
new file mode 100644
index 0000000..8304f2d
--- /dev/null
+++ b/provider/resource_keycloak_saml_client_scope.go
@@ -0,0 +1,152 @@
+package provider
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func resourceKeycloakSamlClientScope() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakSamlClientScopeCreate,
+		Read:   resourceKeycloakSamlClientScopeRead,
+		Delete: resourceKeycloakSamlClientScopeDelete,
+		Update: resourceKeycloakSamlClientScopeUpdate,
+		// This resource can be imported using {{realm}}/{{client_scope_id}}. The Client Scope ID is displayed in the GUI
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakSamlClientScopeImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"name": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"consent_screen_text": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"gui_order": {
+				Type:     schema.TypeInt,
+				Optional: true,
+			},
+		},
+	}
+}
+
+func getSamlClientScopeFromData(data *schema.ResourceData) *keycloak.SamlClientScope {
+	clientScope := &keycloak.SamlClientScope{
+		Id:          data.Id(),
+		RealmId:     data.Get("realm_id").(string),
+		Name:        data.Get("name").(string),
+		Description: data.Get("description").(string),
+	}
+
+	if consentScreenText, ok := data.GetOk("consent_screen_text"); ok {
+		clientScope.Attributes.ConsentScreenText = consentScreenText.(string)
+		clientScope.Attributes.DisplayOnConsentScreen = true
+	} else {
+		clientScope.Attributes.DisplayOnConsentScreen = false
+	}
+
+	// Treat 0 as an empty string for the purpose of omitting the attribute to reset the order
+	if guiOrder := data.Get("gui_order").(int); guiOrder != 0 {
+		clientScope.Attributes.GuiOrder = strconv.Itoa(guiOrder)
+	}
+
+	return clientScope
+}
+
+func setSamlClientScopeData(data *schema.ResourceData, clientScope *keycloak.SamlClientScope) {
+	data.SetId(clientScope.Id)
+
+	data.Set("realm_id", clientScope.RealmId)
+	data.Set("name", clientScope.Name)
+	data.Set("description", clientScope.Description)
+
+	if clientScope.Attributes.DisplayOnConsentScreen {
+		data.Set("consent_screen_text", clientScope.Attributes.ConsentScreenText)
+	}
+
+	if guiOrder, err := strconv.Atoi(clientScope.Attributes.GuiOrder); err == nil {
+		data.Set("gui_order", guiOrder)
+	}
+}
+
+func resourceKeycloakSamlClientScopeCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	clientScope := getSamlClientScopeFromData(data)
+
+	err := keycloakClient.NewSamlClientScope(clientScope)
+	if err != nil {
+		return err
+	}
+
+	setSamlClientScopeData(data, clientScope)
+
+	return resourceKeycloakSamlClientScopeRead(data, meta)
+}
+
+func resourceKeycloakSamlClientScopeRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	clientScope, err := keycloakClient.GetSamlClientScope(realmId, id)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	setSamlClientScopeData(data, clientScope)
+
+	return nil
+}
+
+func resourceKeycloakSamlClientScopeUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	clientScope := getSamlClientScopeFromData(data)
+
+	err := keycloakClient.UpdateSamlClientScope(clientScope)
+	if err != nil {
+		return err
+	}
+
+	setSamlClientScopeData(data, clientScope)
+
+	return nil
+}
+
+func resourceKeycloakSamlClientScopeDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	return keycloakClient.DeleteSamlClientScope(realmId, id)
+}
+
+func resourceKeycloakSamlClientScopeImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+	parts := strings.Split(d.Id(), "/")
+	if len(parts) != 2 {
+		return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{samlClientScopeId}}")
+	}
+
+	d.Set("realm_id", parts[0])
+	d.SetId(parts[1])
+
+	return []*schema.ResourceData{d}, nil
+}
diff --git a/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go b/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go
index bf1504d..0476243 100644
--- a/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go
+++ b/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go
@@ -2,6 +2,7 @@ package provider
 
 import (
 	"fmt"
+
 	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
 )
@@ -25,13 +26,20 @@ func resourceKeycloakUserTemplateImporterIdentityProviderMapper() *schema.Resour
 func getUserTemplateImporterIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) {
 	keycloakClient := meta.(*keycloak.KeycloakClient)
 	rec, _ := getIdentityProviderMapperFromData(data)
+	extraConfig := map[string]interface{}{}
+	if v, ok := data.GetOk("extra_config"); ok {
+		for key, value := range v.(map[string]interface{}) {
+			extraConfig[key] = value
+		}
+	}
 	identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias)
 	if err != nil {
 		return nil, handleNotFoundError(err, data)
 	}
 	rec.IdentityProviderMapper = fmt.Sprintf("%s-username-idp-mapper", identityProvider.ProviderId)
 	rec.Config = &keycloak.IdentityProviderMapperConfig{
-		Template: data.Get("template").(string),
+		Template:    data.Get("template").(string),
+		ExtraConfig: extraConfig,
 	}
 	return rec, nil
 }
@@ -39,5 +47,6 @@ func getUserTemplateImporterIdentityProviderMapperFromData(data *schema.Resource
 func setUserTemplateImporterIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error {
 	setIdentityProviderMapperData(data, identityProviderMapper)
 	data.Set("template", identityProviderMapper.Config.Template)
+	data.Set("extra_config", identityProviderMapper.Config.ExtraConfig)
 	return nil
 }


Test patch:
diff --git a/provider/resource_keycloak_attribute_importer_identity_provider_mapper_test.go b/provider/resource_keycloak_attribute_importer_identity_provider_mapper_test.go
index 1fb8955..e18d6ff 100644
--- a/provider/resource_keycloak_attribute_importer_identity_provider_mapper_test.go
+++ b/provider/resource_keycloak_attribute_importer_identity_provider_mapper_test.go
@@ -2,11 +2,12 @@ package provider
 
 import (
 	"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"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"testing"
 )
 
 func TestAccKeycloakAttributeImporterIdentityProviderMapper_basic(t *testing.T) {
@@ -29,6 +30,27 @@ func TestAccKeycloakAttributeImporterIdentityProviderMapper_basic(t *testing.T)
 	})
 }
 
+func TestAccKeycloakAttributeImporterIdentityProviderMapper_withExtraConfig(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	userAttribute := "terraform-" + acctest.RandString(10)
+	claimName := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAttributeImporterIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, userAttribute, claimName, syncMode),
+				Check:  testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakAttributeImporterIdentityProviderMapper_createAfterManualDestroy(t *testing.T) {
 	var mapper = &keycloak.IdentityProviderMapper{}
 
@@ -63,6 +85,41 @@ func TestAccKeycloakAttributeImporterIdentityProviderMapper_createAfterManualDes
 	})
 }
 
+func TestAccKeycloakAttributeImporterIdentityProviderMapper_withExtraConfig_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.IdentityProviderMapper{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	userAttribute := "terraform-" + acctest.RandString(10)
+	claimName := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAttributeImporterIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, userAttribute, claimName, syncMode),
+				Check:  testAccCheckKeycloakAttributeImporterIdentityProviderMapperFetch("keycloak_attribute_importer_identity_provider_mapper.oidc", mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakAttributeImporterIdentityProviderMapper_basic(realmName, alias, mapperName, userAttribute, claimName),
+				Check:  testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakAttributeImporterIdentityProviderMapper_basicUpdateRealm(t *testing.T) {
 	firstRealm := "terraform-" + acctest.RandString(10)
 	secondRealm := "terraform-" + acctest.RandString(10)
@@ -229,6 +286,34 @@ resource keycloak_attribute_importer_identity_provider_mapper oidc {
 	`, realm, alias, name, userAttribute, claimName)
 }
 
+func testKeycloakAttributeImporterIdentityProviderMapper_withExtraConfig(realm, alias, name, userAttribute, claimName, syncMode string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "oidc" {
+	realm             = "${keycloak_realm.realm.id}"
+	alias             = "%s"
+	authorization_url = "https://example.com/auth"
+	token_url         = "https://example.com/token"
+	client_id         = "example_id"
+	client_secret     = "example_token"
+}
+
+resource keycloak_attribute_importer_identity_provider_mapper oidc {
+	realm                   = "${keycloak_realm.realm.id}"
+	name                    = "%s"
+	identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}"
+	user_attribute          = "%s"
+	claim_name              = "%s"
+	extra_config 			= {
+		syncMode = "%s"    
+	}
+}
+	`, realm, alias, name, userAttribute, claimName, syncMode)
+}
+
 func testKeycloakAttributeImporterIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
diff --git a/provider/resource_keycloak_attribute_to_role_identity_provider_mapper_test.go b/provider/resource_keycloak_attribute_to_role_identity_provider_mapper_test.go
index 4ec296e..13316f1 100644
--- a/provider/resource_keycloak_attribute_to_role_identity_provider_mapper_test.go
+++ b/provider/resource_keycloak_attribute_to_role_identity_provider_mapper_test.go
@@ -2,11 +2,12 @@ package provider
 
 import (
 	"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"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"testing"
 )
 
 func TestAccKeycloakAttributeToRoleIdentityProviderMapper_basic(t *testing.T) {
@@ -30,6 +31,28 @@ func TestAccKeycloakAttributeToRoleIdentityProviderMapper_basic(t *testing.T) {
 	})
 }
 
+func TestAccKeycloakAttributeToRoleIdentityProviderMapper_withExtraConfig(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	role := "terraform-" + acctest.RandString(10)
+	claimName := "terraform-" + acctest.RandString(10)
+	claimValue := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAttributeToRoleIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, role, claimName, claimValue, syncMode),
+				Check:  testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakAttributeToRoleIdentityProviderMapper_createAfterManualDestroy(t *testing.T) {
 	var mapper = &keycloak.IdentityProviderMapper{}
 
@@ -65,6 +88,42 @@ func TestAccKeycloakAttributeToRoleIdentityProviderMapper_createAfterManualDestr
 	})
 }
 
+func TestAccKeycloakAttributeToRoleIdentityProviderMapper_withExtraConfig_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.IdentityProviderMapper{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	role := "terraform-" + acctest.RandString(10)
+	claimName := "terraform-" + acctest.RandString(10)
+	claimValue := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAttributeToRoleIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, role, claimName, claimValue, syncMode),
+				Check:  testAccCheckKeycloakAttributeToRoleIdentityProviderMapperFetch("keycloak_attribute_to_role_identity_provider_mapper.oidc", mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakAttributeToRoleIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, role, claimName, claimValue, syncMode),
+				Check:  testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakAttributeToRoleIdentityProviderMapper_basicUpdateRealm(t *testing.T) {
 	firstRealm := "terraform-" + acctest.RandString(10)
 	secondRealm := "terraform-" + acctest.RandString(10)
@@ -235,6 +294,35 @@ resource keycloak_attribute_to_role_identity_provider_mapper oidc {
 	`, realm, alias, name, role, claimName, claimValue)
 }
 
+func testKeycloakAttributeToRoleIdentityProviderMapper_withExtraConfig(realm, alias, name, role, claimName, claimValue, syncMode string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "oidc" {
+	realm             = "${keycloak_realm.realm.id}"
+	alias             = "%s"
+	authorization_url = "https://example.com/auth"
+	token_url         = "https://example.com/token"
+	client_id         = "example_id"
+	client_secret     = "example_token"
+}
+
+resource keycloak_attribute_to_role_identity_provider_mapper oidc {
+	realm                   = "${keycloak_realm.realm.id}"
+	name                    = "%s"
+	identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}"
+	role                    = "%s"
+	claim_name              = "%s"
+	claim_value             = "%s"
+	extra_config 			= {
+		syncMode = "%s"
+	}
+}
+	`, realm, alias, name, role, claimName, claimValue, syncMode)
+}
+
 func testKeycloakAttributeToRoleIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
diff --git a/provider/resource_keycloak_generic_client_role_mapper_test.go b/provider/resource_keycloak_generic_client_role_mapper_test.go
index f299bb7..5574e24 100644
--- a/provider/resource_keycloak_generic_client_role_mapper_test.go
+++ b/provider/resource_keycloak_generic_client_role_mapper_test.go
@@ -28,6 +28,42 @@ func TestGenericRoleMapper_basic(t *testing.T) {
 	})
 }
 
+func TestGenericRoleMapper_createAfterManualDestroy(t *testing.T) {
+	var role = &keycloak.Role{}
+	var childClient = &keycloak.GenericClient{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	parentClientName := "client1-" + acctest.RandString(10)
+	parentRoleName := "role-" + acctest.RandString(10)
+	childClientName := "client2-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
+					testAccCheckKeycloakRoleFetch("keycloak_role.parent-role", role),
+					testAccCheckKeycloakGenericClientFetch("keycloak_openid_client.child-client", childClient),
+				),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteRoleScopeMapping(childClient.RealmId, childClient.Id, "", role)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
+				Check:  testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
+			},
+		},
+	})
+}
 func TestGenericRoleMapper_import(t *testing.T) {
 	realmName := "terraform-" + acctest.RandString(10)
 	parentClientName := "client1-" + acctest.RandString(10)
@@ -98,38 +134,54 @@ func TestGenericRoleMapperClientScope_import(t *testing.T) {
 	})
 }
 
-func TestGenericRoleMapper_createAfterManualDestroy(t *testing.T) {
+func TestGenericRealmLevelRoleMapperClientScope_basic(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	roleName := "role-" + acctest.RandString(10)
+	clientScopeName := "clientscope-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakGenericRealmLevelRoleMappingClientScope_basic(realmName, roleName, clientScopeName),
+				Check:  testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.clientscope-with-realm-role"),
+			},
+		},
+	})
+}
+
+func TestGenericRealmLevelRoleMapperClientScope_createAfterManualDestroy(t *testing.T) {
 	var role = &keycloak.Role{}
-	var childClient = &keycloak.GenericClient{}
+	var clientScope = &keycloak.OpenidClientScope{}
 
 	realmName := "terraform-" + acctest.RandString(10)
-	parentClientName := "client1-" + acctest.RandString(10)
-	parentRoleName := "role-" + acctest.RandString(10)
-	childClientName := "client2-" + acctest.RandString(10)
+	roleName := "role-" + acctest.RandString(10)
+	clientScopeName := "clientscope-" + acctest.RandString(10)
 
 	resource.Test(t, resource.TestCase{
 		Providers: testAccProviders,
 		PreCheck:  func() { testAccPreCheck(t) },
 		Steps: []resource.TestStep{
 			{
-				Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
+				Config: testKeycloakGenericRealmLevelRoleMappingClientScope_basic(realmName, roleName, clientScopeName),
 				Check: resource.ComposeTestCheckFunc(
-					testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
-					testAccCheckKeycloakRoleFetch("keycloak_role.parent-role", role),
-					testAccCheckKeycloakGenericClientFetch("keycloak_openid_client.child-client", childClient),
+					testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.clientscope-with-realm-role"),
+					testAccCheckKeycloakRoleFetch("keycloak_role.role", role),
+					testAccCheckKeycloakOpenidClientScopeFetch("keycloak_openid_client_scope.clientscope", clientScope),
 				),
 			},
 			{
 				PreConfig: func() {
 					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
 
-					err := keycloakClient.DeleteRoleScopeMapping(childClient.RealmId, childClient.Id, "", role)
+					err := keycloakClient.DeleteRoleScopeMapping(clientScope.RealmId, "", clientScope.Id, role)
 					if err != nil {
 						t.Fatal(err)
 					}
 				},
-				Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
-				Check:  testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
+				Config: testKeycloakGenericRealmLevelRoleMappingClientScope_basic(realmName, roleName, clientScopeName),
+				Check:  testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.clientscope-with-realm-role"),
 			},
 		},
 	})
@@ -235,6 +287,30 @@ resource "keycloak_generic_client_role_mapper" "clientscope-with-client-role" {
 	`, realmName, clientName, roleName, clientScopeName)
 }
 
+func testKeycloakGenericRealmLevelRoleMappingClientScope_basic(realmName, roleName, clientScopeName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_role" "role" {
+  realm_id  = "${keycloak_realm.realm.id}"  
+  name      = "%s"
+}
+
+resource "keycloak_openid_client_scope" "clientscope" {
+	realm_id    = "${keycloak_realm.realm.id}"
+	name        = "%s"
+}
+
+resource "keycloak_generic_client_role_mapper" "clientscope-with-realm-role" {
+  realm_id  = "${keycloak_realm.realm.id}"
+  client_scope_id = "${keycloak_openid_client_scope.clientscope.id}"
+  role_id   = "${keycloak_role.role.id}"
+}
+	`, realmName, roleName, clientScopeName)
+}
+
 func testAccCheckKeycloakScopeMappingExists(resourceName string) resource.TestCheckFunc {
 	return func(s *terraform.State) error {
 		_, ok := s.RootModule().Resources[resourceName]
diff --git a/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper_test.go b/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper_test.go
index a6fea52..9fe7dd2 100644
--- a/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper_test.go
+++ b/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper_test.go
@@ -2,11 +2,12 @@ package provider
 
 import (
 	"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"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"testing"
 )
 
 func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_basic(t *testing.T) {
@@ -30,6 +31,28 @@ func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_basic(t *testing.T)
 	})
 }
 
+func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_withExtraConfig(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	attributeName := "terraform-" + acctest.RandString(10)
+	attributeValue := "terraform-" + acctest.RandString(10)
+	userSession := randomBool()
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakHardcodedAttributeIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, attributeName, attributeValue, userSession, syncMode),
+				Check:  testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_createAfterManualDestroy(t *testing.T) {
 	var mapper = &keycloak.IdentityProviderMapper{}
 
@@ -65,6 +88,41 @@ func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_createAfterManualDe
 	})
 }
 
+func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_withExtraConfig_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.IdentityProviderMapper{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	attributeName := "terraform-" + acctest.RandString(10)
+	attributeValue := "terraform-" + acctest.RandString(10)
+	userSession := randomBool()
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakHardcodedAttributeIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, attributeName, attributeValue, userSession, syncMode),
+				Check:  testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperFetch("keycloak_hardcoded_attribute_identity_provider_mapper.oidc", mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basic(realmName, alias, mapperName, attributeName, attributeValue, userSession),
+				Check:  testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
 func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_basicUpdateRealm(t *testing.T) {
 	firstRealm := "terraform-" + acctest.RandString(10)
 	secondRealm := "terraform-" + acctest.RandString(10)
@@ -236,6 +294,35 @@ resource keycloak_hardcoded_attribute_identity_provider_mapper oidc {
 	`, realm, alias, name, attributeName, attributeValue, userSession)
 }
 
+func testKeycloakHardcodedAttributeIdentityProviderMapper_withExtraConfig(realm, alias, name, attributeName, attributeValue string, userSession bool, syncMode string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "oidc" {
+	realm             = "${keycloak_realm.realm.id}"
+	alias             = "%s"
+	authorization_url = "https://example.com/auth"
+	token_url         = "https://example.com/token"
+	client_id         = "example_id"
+	client_secret     = "example_token"
+}
+
+resource keycloak_hardcoded_attribute_identity_provider_mapper oidc {
+	realm                   = "${keycloak_realm.realm.id}"
+	name                    = "%s"
+	identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}"
+	attribute_name          = "%s"
+	attribute_value         = "%s"
+	user_session            = %t
+	extra_config 			= {
+		syncMode = "%s"
+	}
+}
+	`, realm, alias, name, attributeName, attributeValue, userSession, syncMode)
+}
+
 func testKeycloakHardcodedAttributeIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper, userSession bool) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
diff --git a/provider/resource_keycloak_hardcoded_role_identity_provider_mapper_test.go b/provider/resource_keycloak_hardcoded_role_identity_provider_mapper_test.go
index 75f5893..bf2e1bd 100644
--- a/provider/resource_keycloak_hardcoded_role_identity_provider_mapper_test.go
+++ b/provider/resource_keycloak_hardcoded_role_identity_provider_mapper_test.go
@@ -2,11 +2,12 @@ package provider
 
 import (
 	"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"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"testing"
 )
 
 func TestAccKeycloakHardcodedRoleIdentityProviderMapper_basic(t *testing.T) {
@@ -28,6 +29,26 @@ func TestAccKeycloakHardcodedRoleIdentityProviderMapper_basic(t *testing.T) {
 	})
 }
 
+func TestAccKeycloakHardcodedRoleIdentityProviderMapper_withExtraConfig(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	role := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakHardcodedRoleIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, role, syncMode),
+				Check:  testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakHardcodedRoleIdentityProviderMapper_createAfterManualDestroy(t *testing.T) {
 	var mapper = &keycloak.IdentityProviderMapper{}
 
@@ -61,6 +82,40 @@ func TestAccKeycloakHardcodedRoleIdentityProviderMapper_createAfterManualDestroy
 	})
 }
 
+func TestAccKeycloakHardcodedRoleIdentityProviderMapper_withExtraConfig_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.IdentityProviderMapper{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	role := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakHardcodedRoleIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, role, syncMode),
+				Check:  testAccCheckKeycloakHardcodedRoleIdentityProviderMapperFetch("keycloak_hardcoded_role_identity_provider_mapper.oidc", mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakHardcodedRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role),
+				Check:  testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakHardcodedRoleIdentityProviderMapper_basicUpdateRealm(t *testing.T) {
 	firstRealm := "terraform-" + acctest.RandString(10)
 	secondRealm := "terraform-" + acctest.RandString(10)
@@ -223,6 +278,33 @@ resource keycloak_hardcoded_role_identity_provider_mapper oidc {
 	`, realm, alias, name, role)
 }
 
+func testKeycloakHardcodedRoleIdentityProviderMapper_withExtraConfig(realm, alias, name, role, syncMode string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "oidc" {
+	realm             = "${keycloak_realm.realm.id}"
+	alias             = "%s"
+	authorization_url = "https://example.com/auth"
+	token_url         = "https://example.com/token"
+	client_id         = "example_id"
+	client_secret     = "example_token"
+}
+
+resource keycloak_hardcoded_role_identity_provider_mapper oidc {
+	realm                   = "${keycloak_realm.realm.id}"
+	name                    = "%s"
+	identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}"
+	role                    = "%s"
+	extra_config 			= {
+		syncMode = "%s"
+	}
+}
+	`, realm, alias, name, role, syncMode)
+}
+
 func testKeycloakHardcodedRoleIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
diff --git a/provider/resource_keycloak_openid_client_scope_test.go b/provider/resource_keycloak_openid_client_scope_test.go
index f65701a..e5f9bed 100644
--- a/provider/resource_keycloak_openid_client_scope_test.go
+++ b/provider/resource_keycloak_openid_client_scope_test.go
@@ -2,11 +2,13 @@ package provider
 
 import (
 	"fmt"
+	"strconv"
+	"testing"
+
 	"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"
-	"testing"
 )
 
 func TestAccKeycloakClientScope_basic(t *testing.T) {
@@ -119,6 +121,64 @@ func TestAccKeycloakClientScope_consentScreenText(t *testing.T) {
 	})
 }
 
+func TestAccKeycloakClientScope_includeInTokenScope(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+	includeInTokenScope := false
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakClientScopeExistsWithCorrectProtocol("keycloak_openid_client_scope.client_scope"),
+			},
+			{
+				Config: testKeycloakClientScope_withIncludeInTokenScope(realmName, clientScopeName, includeInTokenScope),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakClientScopeExistsWithCorrectProtocol("keycloak_openid_client_scope.client_scope"),
+					testAccCheckKeycloakClientScopeExistsWithCorrectIncludeInTokenScope("keycloak_openid_client_scope.client_scope", includeInTokenScope),
+				),
+			},
+			{
+				Config: testKeycloakClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakClientScopeExistsWithCorrectProtocol("keycloak_openid_client_scope.client_scope"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakClientScope_guiOrder(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+	guiOrder := acctest.RandIntRange(0, 1000)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakClientScopeExistsWithCorrectProtocol("keycloak_openid_client_scope.client_scope"),
+			},
+			{
+				Config: testKeycloakClientScope_withGuiOrder(realmName, clientScopeName, guiOrder),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakClientScopeExistsWithCorrectProtocol("keycloak_openid_client_scope.client_scope"),
+					testAccCheckKeycloakClientScopeExistsWithCorrectGuiOrder("keycloak_openid_client_scope.client_scope", guiOrder),
+				),
+			},
+			{
+				Config: testKeycloakClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakClientScopeExistsWithCorrectProtocol("keycloak_openid_client_scope.client_scope"),
+			},
+		},
+	})
+}
+
 func testAccCheckKeycloakClientScopeExistsWithCorrectProtocol(resourceName string) resource.TestCheckFunc {
 	return func(s *terraform.State) error {
 		clientScope, err := getClientScopeFromState(s, resourceName)
@@ -134,6 +194,36 @@ func testAccCheckKeycloakClientScopeExistsWithCorrectProtocol(resourceName strin
 	}
 }
 
+func testAccCheckKeycloakClientScopeExistsWithCorrectIncludeInTokenScope(resourceName string, includeInTokenScope bool) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		clientScope, err := getClientScopeFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if clientScope.Attributes.IncludeInTokenScope != keycloak.KeycloakBoolQuoted(includeInTokenScope) {
+			return fmt.Errorf("expected saml client includeInTokenScope to have %t, but got %t", includeInTokenScope, clientScope.Attributes.IncludeInTokenScope)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakClientScopeExistsWithCorrectGuiOrder(resourceName string, guiOrder int) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		clientScope, err := getClientScopeFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if clientScope.Attributes.GuiOrder != strconv.Itoa(guiOrder) {
+			return fmt.Errorf("expected saml client guiOrder to have %d, but got %s", guiOrder, clientScope.Attributes.GuiOrder)
+		}
+
+		return nil
+	}
+}
+
 func testAccCheckKeycloakClientScopeFetch(resourceName string, clientScope *keycloak.OpenidClientScope) resource.TestCheckFunc {
 	return func(s *terraform.State) error {
 		fetchedClientScope, err := getClientScopeFromState(s, resourceName)
@@ -236,6 +326,40 @@ resource "keycloak_openid_client_scope" "client_scope" {
 	`, realm, clientScopeName, consentText)
 }
 
+func testKeycloakClientScope_withIncludeInTokenScope(realm, clientScopeName string, includeInTokenScope bool) 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}"
+
+	description         = "test description"
+
+	include_in_token_scope = %t
+}
+	`, realm, clientScopeName, includeInTokenScope)
+}
+
+func testKeycloakClientScope_withGuiOrder(realm, clientScopeName string, guiOrder int) 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}"
+
+	description         = "test description"
+
+	gui_order           = %d
+}
+	`, realm, clientScopeName, guiOrder)
+}
+
 func testKeycloakClientScope_updateRealmBefore(realmOne, realmTwo, clientScopeName string) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm_1" {
diff --git a/provider/resource_keycloak_saml_client_default_scopes_test.go b/provider/resource_keycloak_saml_client_default_scopes_test.go
new file mode 100644
index 0000000..f469b29
--- /dev/null
+++ b/provider/resource_keycloak_saml_client_default_scopes_test.go
@@ -0,0 +1,527 @@
+package provider
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+	"testing"
+
+	"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"
+)
+
+// All saml clients in Keycloak will automatically have these scopes listed as "default client scopes".
+var preAssignedDefaultSamlClientScopes = []string{"role_list"}
+
+func TestAccKeycloakSamlClientDefaultScopes_basic(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+	clientScope := "terraform-client-scope-" + acctest.RandString(10)
+
+	clientScopes := append(preAssignedDefaultSamlClientScopes, clientScope)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientDefaultScopes_basic(realm, client, clientScope),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", clientScopes),
+			},
+			// we need a separate test step for destroy instead of using CheckDestroy because this resource is implicitly
+			// destroyed at the end of each test via destroying clients
+			{
+				Config: testKeycloakSamlClientDefaultScopes_noDefaultScopes(realm, client, clientScope),
+				Check:  testAccCheckKeycloakSamlClientHasNoDefaultScopes("keycloak_saml_client.client"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientDefaultScopes_updateClientForceNew(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	clientOne := "terraform-client-" + acctest.RandString(10)
+	clientTwo := "terraform-client-" + acctest.RandString(10)
+	clientScope := "terraform-client-scope-" + acctest.RandString(10)
+
+	clientScopes := append(preAssignedDefaultSamlClientScopes, clientScope)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientDefaultScopes_basic(realm, clientOne, clientScope),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", clientScopes),
+			},
+			{
+				Config: testKeycloakSamlClientDefaultScopes_basic(realm, clientTwo, clientScope),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", clientScopes),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientDefaultScopes_updateInPlace(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+	clientScope := "terraform-client-scope-" + acctest.RandString(10)
+
+	allClientScopes := append(preAssignedDefaultSamlClientScopes, clientScope)
+
+	clientScopeToRemove := allClientScopes[acctest.RandIntRange(0, 2)]
+	var subsetOfClientScopes []string
+	for _, cs := range allClientScopes {
+		if cs != clientScopeToRemove {
+			subsetOfClientScopes = append(subsetOfClientScopes, cs)
+		}
+	}
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			// init
+			{
+				Config: testKeycloakSamlClientDefaultScopes_listOfScopes(realm, client, clientScope, allClientScopes),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", allClientScopes),
+			},
+			// remove
+			{
+				Config: testKeycloakSamlClientDefaultScopes_listOfScopes(realm, client, clientScope, subsetOfClientScopes),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", subsetOfClientScopes),
+			},
+			// add
+			{
+				Config: testKeycloakSamlClientDefaultScopes_listOfScopes(realm, client, clientScope, allClientScopes),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", allClientScopes),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientDefaultScopes_validateClientDoesNotExist(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+	clientScope := "terraform-client-scope-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakSamlClientDefaultScopes_validationNoClient(realm, client, clientScope),
+				ExpectError: regexp.MustCompile("validation error: client with id .+ does not exist"),
+			},
+		},
+	})
+}
+
+// if a default client scope is manually detached from a client with default scopes controlled by this resource, terraform should add it again
+func TestAccKeycloakSamlClientDefaultScopes_authoritativeAdd(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+	clientScopes := append(preAssignedDefaultSamlClientScopes,
+		"terraform-client-scope-"+acctest.RandString(10),
+		"terraform-client-scope-"+acctest.RandString(10),
+		"terraform-client-scope-"+acctest.RandString(10),
+	)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientDefaultScopes_multipleClientScopes(realm, client, clientScopes, clientScopes),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", clientScopes),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					client, err := keycloakClient.GetSamlClientByClientId(realm, client)
+					if err != nil {
+						t.Fatal(err)
+					}
+
+					clientToManuallyDetach := clientScopes[acctest.RandIntRange(0, len(clientScopes)-1)]
+					err = keycloakClient.DetachSamlClientDefaultScopes(realm, client.Id, []string{clientToManuallyDetach})
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakSamlClientDefaultScopes_multipleClientScopes(realm, client, clientScopes, clientScopes),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", clientScopes),
+			},
+		},
+	})
+}
+
+// if a default client scope is manually attached to a client with default scopes controlled by this resource, terraform should detach it
+func TestAccKeycloakSamlClientDefaultScopes_authoritativeRemove(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+
+	randomClientScopes := []string{
+		"terraform-client-scope-" + acctest.RandString(10),
+		"terraform-client-scope-" + acctest.RandString(10),
+		"terraform-client-scope-" + acctest.RandString(10),
+	}
+	allClientScopes := append(preAssignedDefaultSamlClientScopes, randomClientScopes...)
+
+	clientToManuallyAttach := randomClientScopes[acctest.RandIntRange(0, len(randomClientScopes)-1)]
+	var attachedClientScopes []string
+	for _, clientScope := range allClientScopes {
+		if clientScope != clientToManuallyAttach {
+			attachedClientScopes = append(attachedClientScopes, clientScope)
+		}
+	}
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientDefaultScopes_multipleClientScopes(realm, client, allClientScopes, attachedClientScopes),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", attachedClientScopes),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					client, err := keycloakClient.GetSamlClientByClientId(realm, client)
+					if err != nil {
+						t.Fatal(err)
+					}
+
+					err = keycloakClient.AttachSamlClientDefaultScopes(realm, client.Id, []string{clientToManuallyAttach})
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakSamlClientDefaultScopes_multipleClientScopes(realm, client, allClientScopes, attachedClientScopes),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", attachedClientScopes),
+					testAccCheckKeycloakSamlClientDefaultScopeIsNotAttached("keycloak_saml_client_default_scopes.default_scopes", clientToManuallyAttach),
+				),
+			},
+		},
+	})
+}
+
+// this resource doesn't support import because it can be created even if the desired state already exists in keycloak
+func TestAccKeycloakSamlClientDefaultScopes_noImportNeeded(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+	clientScope := "terraform-client-scope-" + acctest.RandString(10)
+
+	clientScopes := append(preAssignedDefaultSamlClientScopes, clientScope)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientDefaultScopes_noDefaultScopes(realm, client, clientScope),
+				Check:  testAccCheckKeycloakSamlClientDefaultScopeIsNotAttached("keycloak_saml_client.client", clientScope),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					samlClient, err := keycloakClient.GetSamlClientByClientId(realm, client)
+					if err != nil {
+						t.Fatal(err)
+					}
+
+					err = keycloakClient.AttachSamlClientDefaultScopes(realm, samlClient.Id, clientScopes)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakSamlClientDefaultScopes_basic(realm, client, clientScope),
+				Check:  testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client_default_scopes.default_scopes", clientScopes),
+			},
+		},
+	})
+}
+
+// by default, keycloak saml clients have the default scopes "role_list",
+// attached. if you create this resource with only one scope, it
+// won't remove these two scopes, because the creation of a new resource should not
+// result in anything destructive. thus, a following plan will not be empty, as terraform
+// will think it needs to remove these scopes, which is okay to do during an update
+func TestAccKeycloakSamlClientDefaultScopes_profileAndEmailDefaultScopes(t *testing.T) {
+	realm := "terraform-realm-" + acctest.RandString(10)
+	client := "terraform-client-" + acctest.RandString(10)
+	clientScope := "terraform-client-scope-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers: testAccProviders,
+		PreCheck:  func() { testAccPreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				Config:             testKeycloakSamlClientDefaultScopes_listOfScopes(realm, client, clientScope, []string{clientScope}),
+				Check:              testAccCheckKeycloakSamlClientHasDefaultScopes("keycloak_saml_client.client", append(preAssignedDefaultSamlClientScopes, clientScope)),
+				ExpectNonEmptyPlan: true,
+			},
+		},
+	})
+}
+
+func getDefaultSamlClientScopesFromState(resourceName string, s *terraform.State) ([]*keycloak.SamlClientScope, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	realm := rs.Primary.Attributes["realm_id"]
+
+	var client string
+	if strings.HasPrefix(resourceName, "keycloak_saml_client_default_scopes") {
+		client = rs.Primary.Attributes["client_id"]
+	} else {
+		client = rs.Primary.ID
+	}
+
+	keycloakDefaultSamlClientScopes, err := keycloakClient.GetSamlClientDefaultScopes(realm, client)
+	if err != nil {
+		return nil, err
+	}
+
+	return keycloakDefaultSamlClientScopes, nil
+}
+
+func testAccCheckKeycloakSamlClientHasDefaultScopes(resourceName string, tfDefaultClientScopes []string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		keycloakDefaultClientScopes, err := getDefaultSamlClientScopesFromState(resourceName, s)
+		if err != nil {
+			return err
+		}
+
+		for _, tfDefaultClientScope := range tfDefaultClientScopes {
+			found := false
+
+			for _, keycloakDefaultScope := range keycloakDefaultClientScopes {
+				if keycloakDefaultScope.Name == tfDefaultClientScope {
+					found = true
+
+					break
+				}
+			}
+
+			if !found {
+				return fmt.Errorf("default scope %s is not assigned to client", tfDefaultClientScope)
+			}
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakSamlClientHasNoDefaultScopes(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		keycloakDefaultClientScopes, err := getDefaultSamlClientScopesFromState(resourceName, s)
+		if err != nil {
+			return err
+		}
+
+		if numberOfDefaultScopes := len(keycloakDefaultClientScopes); numberOfDefaultScopes != 0 {
+			return fmt.Errorf("expected client to have no assigned default scopes, but it has %d", numberOfDefaultScopes)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakSamlClientDefaultScopeIsNotAttached(resourceName, clientScope string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		keycloakDefaultClientScopes, err := getDefaultSamlClientScopesFromState(resourceName, s)
+		if err != nil {
+			return err
+		}
+
+		for _, keycloakDefaultClientScope := range keycloakDefaultClientScopes {
+			if keycloakDefaultClientScope.Name == clientScope {
+				return fmt.Errorf("expected client scope with name %s to not be attached to client", clientScope)
+			}
+		}
+
+		return nil
+	}
+}
+
+func testKeycloakSamlClientDefaultScopes_basic(realm, client, clientScope string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client" "client" {
+	client_id   = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+	
+	sign_documents          = false
+	sign_assertions         = true
+	include_authn_statement = true
+
+	signing_certificate     = "${file("misc/saml-cert.pem")}"
+	signing_private_key     = "${file("misc/saml-key.pem")}"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name        = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+
+	description = "test description"
+}
+
+resource "keycloak_saml_client_default_scopes" "default_scopes" {
+	realm_id       = "${keycloak_realm.realm.id}"
+	client_id      = "${keycloak_saml_client.client.id}"
+	default_scopes = [
+		"role_list",
+		"${keycloak_saml_client_scope.client_scope.name}"
+	]
+}
+	`, realm, client, clientScope)
+}
+
+func testKeycloakSamlClientDefaultScopes_noDefaultScopes(realm, client, clientScope string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client" "client" {
+	client_id   = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+	
+	sign_documents          = false
+	sign_assertions         = true
+	include_authn_statement = true
+
+	signing_certificate     = "${file("misc/saml-cert.pem")}"
+	signing_private_key     = "${file("misc/saml-key.pem")}"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name        = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+
+	description = "test description"
+}
+	`, realm, client, clientScope)
+}
+
+func testKeycloakSamlClientDefaultScopes_listOfScopes(realm, client, clientScope string, listOfDefaultScopes []string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client" "client" {
+	client_id   = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+	
+	sign_documents          = false
+	sign_assertions         = true
+	include_authn_statement = true
+
+	signing_certificate     = "${file("misc/saml-cert.pem")}"
+	signing_private_key     = "${file("misc/saml-key.pem")}"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name        = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+
+	description = "test description"
+}
+
+resource "keycloak_saml_client_default_scopes" "default_scopes" {
+	realm_id       = "${keycloak_realm.realm.id}"
+	client_id      = "${keycloak_saml_client.client.id}"
+	default_scopes = %s
+
+	depends_on = ["keycloak_saml_client_scope.client_scope"]
+}
+	`, realm, client, clientScope, arrayOfStringsForTerraformResource(listOfDefaultScopes))
+}
+
+func testKeycloakSamlClientDefaultScopes_validationNoClient(realm, client, clientScope string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name        = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+
+	description = "test description"
+}
+
+resource "keycloak_saml_client_default_scopes" "default_scopes" {
+	realm_id       = "${keycloak_realm.realm.id}"
+	client_id      = "%s"
+	default_scopes = [
+		"role_list",
+		"${keycloak_saml_client_scope.client_scope.name}"
+	]
+}
+	`, realm, clientScope, client)
+}
+
+func testKeycloakSamlClientDefaultScopes_multipleClientScopes(realm, client string, allClientScopes, attachedClientScopes []string) string {
+	var clientScopeResources strings.Builder
+	for _, clientScope := range allClientScopes {
+		if strings.HasPrefix(clientScope, "terraform") {
+			clientScopeResources.WriteString(fmt.Sprintf(`
+resource "keycloak_saml_client_scope" "client_scope_%s" {
+	name        = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+}
+		`, clientScope, clientScope))
+		}
+	}
+
+	var attachedClientScopesInterpolated []string
+	for _, attachedClientScope := range attachedClientScopes {
+		if strings.HasPrefix(attachedClientScope, "terraform") {
+			attachedClientScopesInterpolated = append(attachedClientScopesInterpolated, fmt.Sprintf("${keycloak_saml_client_scope.client_scope_%s.name}", attachedClientScope))
+		} else {
+			attachedClientScopesInterpolated = append(attachedClientScopesInterpolated, attachedClientScope)
+		}
+	}
+
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client" "client" {
+	client_id   = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+	
+	sign_documents          = false
+	sign_assertions         = true
+	include_authn_statement = true
+
+	signing_certificate     = "${file("misc/saml-cert.pem")}"
+	signing_private_key     = "${file("misc/saml-key.pem")}"
+}
+
+%s
+
+resource "keycloak_saml_client_default_scopes" "default_scopes" {
+	realm_id       = "${keycloak_realm.realm.id}"
+	client_id      = "${keycloak_saml_client.client.id}"
+	default_scopes = %s
+}
+	`, realm, client, clientScopeResources.String(), arrayOfStringsForTerraformResource(attachedClientScopesInterpolated))
+}
diff --git a/provider/resource_keycloak_saml_client_scope_test.go b/provider/resource_keycloak_saml_client_scope_test.go
new file mode 100644
index 0000000..9a4e0a6
--- /dev/null
+++ b/provider/resource_keycloak_saml_client_scope_test.go
@@ -0,0 +1,334 @@
+package provider
+
+import (
+	"fmt"
+	"strconv"
+	"testing"
+
+	"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"
+)
+
+func TestAccKeycloakSamlClientScope_basic(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakSamlClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+			{
+				ResourceName:        "keycloak_saml_client_scope.client_scope",
+				ImportState:         true,
+				ImportStateVerify:   true,
+				ImportStateIdPrefix: realmName + "/",
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientScope_createAfterManualDestroy(t *testing.T) {
+	var clientScope = &keycloak.SamlClientScope{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakSamlClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+					testAccCheckKeycloakSamlClientScopeFetch("keycloak_saml_client_scope.client_scope", clientScope),
+				),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteSamlClientScope(clientScope.RealmId, clientScope.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientScope_updateRealm(t *testing.T) {
+	realmOne := "terraform-" + acctest.RandString(10)
+	realmTwo := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakSamlClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientScope_updateRealmBefore(realmOne, realmTwo, clientScopeName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+					testAccCheckKeycloakSamlClientScopeBelongsToRealm("keycloak_saml_client_scope.client_scope", realmOne),
+				),
+			},
+			{
+				Config: testKeycloakSamlClientScope_updateRealmAfter(realmOne, realmTwo, clientScopeName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+					testAccCheckKeycloakSamlClientScopeBelongsToRealm("keycloak_saml_client_scope.client_scope", realmTwo),
+				),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientScope_consentScreenText(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakSamlClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+			{
+				Config: testKeycloakSamlClientScope_withConsentText(realmName, clientScopeName, acctest.RandString(10)),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+			{
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakSamlClientScope_guiOrder(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientScopeName := "terraform-" + acctest.RandString(10)
+	guiOrder := acctest.RandIntRange(0, 1000)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakSamlClientScopeDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+			{
+				Config: testKeycloakSamlClientScope_withGuiOrder(realmName, clientScopeName, guiOrder),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+					testAccCheckKeycloakSamlClientScopeExistsWithCorrectGuiOrder("keycloak_saml_client_scope.client_scope", guiOrder),
+				),
+			},
+			{
+				Config: testKeycloakSamlClientScope_basic(realmName, clientScopeName),
+				Check:  testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol("keycloak_saml_client_scope.client_scope"),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakSamlClientScopeExistsWithCorrectProtocol(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		clientScope, err := getSamlClientScopeFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if clientScope.Protocol != "saml" {
+			return fmt.Errorf("expected saml client scope to have saml protocol, but got %s", clientScope.Protocol)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakSamlClientScopeExistsWithCorrectGuiOrder(resourceName string, guiOrder int) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		clientScope, err := getSamlClientScopeFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if clientScope.Attributes.GuiOrder != strconv.Itoa(guiOrder) {
+			return fmt.Errorf("expected saml client guiOrder to have %d, but got %s", guiOrder, clientScope.Attributes.GuiOrder)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakSamlClientScopeFetch(resourceName string, clientScope *keycloak.SamlClientScope) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedClientScope, err := getSamlClientScopeFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		clientScope.Id = fetchedClientScope.Id
+		clientScope.RealmId = fetchedClientScope.RealmId
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakSamlClientScopeBelongsToRealm(resourceName, realm string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		clientScope, err := getSamlClientScopeFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if clientScope.RealmId != realm {
+			return fmt.Errorf("expected saml client scope %s to have realm_id of %s, but got %s", clientScope.Id, realm, clientScope.RealmId)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakSamlClientScopeDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_saml_client_scope" {
+				continue
+			}
+
+			id := rs.Primary.ID
+			realm := rs.Primary.Attributes["realm_id"]
+
+			keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+			clientScope, _ := keycloakClient.GetSamlClientScope(realm, id)
+			if clientScope != nil {
+				return fmt.Errorf("saml client scope %s still exists", id)
+			}
+		}
+
+		return nil
+	}
+}
+
+func getSamlClientScopeFromState(s *terraform.State, resourceName string) (*keycloak.SamlClientScope, error) {
+	keycloakClientScope := 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"]
+
+	clientScope, err := keycloakClientScope.GetSamlClientScope(realm, id)
+	if err != nil {
+		return nil, fmt.Errorf("error getting saml client scope %s: %s", id, err)
+	}
+
+	return clientScope, nil
+}
+
+func testKeycloakSamlClientScope_basic(realm, clientScopeName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name        = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+
+	description = "test description"
+}
+	`, realm, clientScopeName)
+}
+
+func testKeycloakSamlClientScope_withConsentText(realm, clientScopeName, consentText string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name                = "%s"
+	realm_id            = "${keycloak_realm.realm.id}"
+
+	description         = "test description"
+
+	consent_screen_text = "%s"
+}
+	`, realm, clientScopeName, consentText)
+}
+
+func testKeycloakSamlClientScope_withGuiOrder(realm, clientScopeName string, guiOrder int) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name                = "%s"
+	realm_id            = "${keycloak_realm.realm.id}"
+
+	description         = "test description"
+
+	gui_order           = %d
+}
+	`, realm, clientScopeName, guiOrder)
+}
+
+func testKeycloakSamlClientScope_updateRealmBefore(realmOne, realmTwo, clientScopeName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm_1" {
+	realm = "%s"
+}
+
+resource "keycloak_realm" "realm_2" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name      = "%s"
+	realm_id  = "${keycloak_realm.realm_1.id}"
+}
+	`, realmOne, realmTwo, clientScopeName)
+}
+
+func testKeycloakSamlClientScope_updateRealmAfter(realmOne, realmTwo, clientScopeName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm_1" {
+	realm = "%s"
+}
+
+resource "keycloak_realm" "realm_2" {
+	realm = "%s"
+}
+
+resource "keycloak_saml_client_scope" "client_scope" {
+	name      = "%s"
+	realm_id  = "${keycloak_realm.realm_2.id}"
+}
+	`, realmOne, realmTwo, clientScopeName)
+}
diff --git a/provider/resource_keycloak_user_template_importer_identity_provider_mapper_test.go b/provider/resource_keycloak_user_template_importer_identity_provider_mapper_test.go
index 0506894..386505f 100644
--- a/provider/resource_keycloak_user_template_importer_identity_provider_mapper_test.go
+++ b/provider/resource_keycloak_user_template_importer_identity_provider_mapper_test.go
@@ -2,11 +2,12 @@ package provider
 
 import (
 	"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"
 	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
-	"testing"
 )
 
 func TestAccKeycloakUserTemplateIdentityProviderMapper_basic(t *testing.T) {
@@ -28,6 +29,26 @@ func TestAccKeycloakUserTemplateIdentityProviderMapper_basic(t *testing.T) {
 	})
 }
 
+func TestAccKeycloakUserTemplateIdentityProviderMapper_withExtraConfig(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	template := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakUserTemplateIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, template, syncMode),
+				Check:  testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakUserTemplateIdentityProviderMapper_createAfterManualDestroy(t *testing.T) {
 	var mapper = &keycloak.IdentityProviderMapper{}
 
@@ -61,6 +82,40 @@ func TestAccKeycloakUserTemplateIdentityProviderMapper_createAfterManualDestroy(
 	})
 }
 
+func TestAccKeycloakUserTemplateIdentityProviderMapper_withExtraConfig_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.IdentityProviderMapper{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	mapperName := "terraform-" + acctest.RandString(10)
+	alias := "terraform-" + acctest.RandString(10)
+	template := "terraform-" + acctest.RandString(10)
+	syncMode := "terraform-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakUserTemplateIdentityProviderMapper_withExtraConfig(realmName, alias, mapperName, template, syncMode),
+				Check:  testAccCheckKeycloakUserTemplateIdentityProviderMapperFetch("keycloak_user_template_importer_identity_provider_mapper.oidc", mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakUserTemplateIdentityProviderMapper_basic(realmName, alias, mapperName, template),
+				Check:  testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.oidc"),
+			},
+		},
+	})
+}
+
 func TestAccKeycloakUserTemplateIdentityProviderMapper_basicUpdateRealm(t *testing.T) {
 	firstRealm := "terraform-" + acctest.RandString(10)
 	secondRealm := "terraform-" + acctest.RandString(10)
@@ -223,6 +278,33 @@ resource keycloak_user_template_importer_identity_provider_mapper oidc {
 	`, realm, alias, name, template)
 }
 
+func testKeycloakUserTemplateIdentityProviderMapper_withExtraConfig(realm, alias, name, template, syncMode string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "oidc" {
+	realm             = "${keycloak_realm.realm.id}"
+	alias             = "%s"
+	authorization_url = "https://example.com/auth"
+	token_url         = "https://example.com/token"
+	client_id         = "example_id"
+	client_secret     = "example_token"
+}
+
+resource keycloak_user_template_importer_identity_provider_mapper oidc {
+	realm                   = "${keycloak_realm.realm.id}"
+	name                    = "%s"
+	identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}"
+	template                = "%s"
+	extra_config 			= {
+		syncMode = "%s"
+	}
+}
+	`, realm, alias, name, template, syncMode)
+}
+
 func testKeycloakUserTemplateIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {