Repository: mrparkers/terraform-provider-keycloak
Pull request #318 (2020-06-12 10:01:21)

resource to enable the token exchange idp permission
resource to enable the token exchange idp permission  and auto create of client policy

This is part of a preview keycloak feature. You need to enable this feature to be able to use this resource.
More information about enabling the preview feature can be found here: https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange

This implies that I enabled this feature in the docker-compose file.
This also implies that the tests will fail in CIRCLE-CI

@mrparkers  What is your opinion on this?
Should I enable this preview feature in CIRCLE_CI or do I add a check in test_utils based on a env variable?
I did not see a way to detect if this feature is enabled via server_info, thus we can not use this server_info approach.




tomrutsaert: Strange all the tests passed. I did not expect this....
mrparkers: Nice work on this @tomrutsaert!  I think it's pretty funny that the feature flag only toggles its visibility in the UI.  We should probably just leave that toggle enabled for example purposes.

Patch:
diff --git a/docker-compose.yml b/docker-compose.yml
index e0bc808..7088737 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,7 +18,7 @@ services:
     - 8389:389
   keycloak:
     image:  jboss/keycloak:10.0.2
-    command: -b 0.0.0.0 -Dkeycloak.profile.feature.upload_scripts=enabled
+    command: -b 0.0.0.0 -Dkeycloak.profile.feature.upload_scripts=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled -Dkeycloak.profile.feature.token_exchange=enabled
     depends_on:
     - postgres
     - openldap
diff --git a/docs/resources/keycloak_identity_provider_token_exchange_scope_permission.md b/docs/resources/keycloak_identity_provider_token_exchange_scope_permission.md
new file mode 100644
index 0000000..2fb6cd8
--- /dev/null
+++ b/docs/resources/keycloak_identity_provider_token_exchange_scope_permission.md
@@ -0,0 +1,88 @@
+# keycloak_identity_provider_token_exchange_scope_permission
+
+Allows you to manage Identity Provider "Token exchange" Scope Based Permissions.
+
+This is part of a preview keycloak feature. You need to enable this feature to be able to use this resource.
+More information about enabling the preview feature can be found here: https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange
+
+When enabling Identity Provider Permissions, Keycloak does several things automatically:
+1. Enable Authorization on build-in realm-management client
+1. Create a "token-exchange" scope
+1. Create a resource representing the identity provider
+1. Create a scope based permission for the "token-exchange" scope and identity provider resource
+
+The only thing that is missing is a policy set on the permission.
+As the policy lives within the context of the realm-management client, you cannot create a policy resource and link to from with your _.tf_ file. This would also cause an implicit cycle dependency.
+Thus, the only way to manage this in terraform is to create and manage the policy internally from within this terraform resource itself.
+At the moment only a client policy type is supported. The client policy will automatically be created for the clients parameter.
+
+### Example Usage
+
+```hcl
+resource "keycloak_realm" "token-exchange_realm" {
+  realm   = "token-exchange_destination_realm"
+  enabled = true
+}
+
+resource keycloak_oidc_identity_provider token-exchange_my_oidc_idp {
+  realm              = keycloak_realm.token-exchange_realm.id
+  alias              = "myIdp"
+  authorization_url  = "http://localhost:8080/auth/realms/someRealm/protocol/openid-connect/auth"
+  token_url          = "http://localhost:8080/auth/realms/someRealm/protocol/openid-connect/token"
+  client_id          = "clientId"
+  client_secret      = "secret"
+  default_scopes     = "openid"
+}
+
+resource "keycloak_openid_client" "token-exchange_webapp_client" {
+  realm_id              = keycloak_realm.token-exchange_realm.id
+  name                  = "webapp_client"
+  client_id             = "webapp_client"
+  client_secret         = "secret"
+  description           = "a webapp client on the destination realm"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+//relevant part
+resource "keycloak_identity_provider_token_exchange_scope_permission" "oidc_idp_permission" {
+  realm_id       = keycloak_realm.token-exchange_realm.id
+  provider_alias = keycloak_oidc_identity_provider.token-exchange_my_oidc_idp.alias
+  policy_type    = "client"
+  clients        = [keycloak_openid_client.token-exchange_webapp_client.id]
+}
+```
+
+### Argument Reference
+
+The following arguments are supported:
+
+- `realm_id` - (Required) The realm this group exists in.
+- `provider_alias` - (Required) Alias of the identity provider.
+- `policy_type` - (Optional) Defaults to "client" This is also the only value policy type supported by this provider.
+- `clients` - (Required) Ids of the clients for which a policy will be created and set on scope based token exchange permission.
+
+### Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+- `policy_id` - Policy id that will be set on the scope based token exchange permission automatically created by enabling permissions on the reference identity provider.
+- `authorization_resource_server_id` - Resource server id representing the realm management client on which this permission is managed.
+- `authorization_idp_resource_id` - Resource id representing the identity provider, this automatically created by keycloak.
+- `authorization_token_exchange_scope_permission_id` - Permission id representing the Permission with scope 'Token Exchange' and the resource 'authorization_idp_resource_id', this automatically created by keycloak, the policy id will be set on this permission.
+
+
+### Import
+
+This resource can be imported using the format
+`{{realm_id}}/{{provider_alias}}`, where `provider_alias` is the alias that you assign to the identity provider upon creation.
+
+Example:
+
+```bash
+$ terraform import keycloak_identity_provider_token_exchange_scope_permission.my_permission my-realm/my_idp
+```
+
diff --git a/example/external_token_exchange_example.tf b/example/external_token_exchange_example.tf
new file mode 100644
index 0000000..86e3f29
--- /dev/null
+++ b/example/external_token_exchange_example.tf
@@ -0,0 +1,57 @@
+resource "keycloak_realm" "token-exchange_source_realm" {
+  realm   = "token-exchange_source_realm"
+  enabled = true
+}
+
+resource "keycloak_openid_client" "token-exchange_destination_client" {
+  realm_id              = keycloak_realm.token-exchange_source_realm.id
+  name                  = "destination_client"
+  client_id             = "destination_client"
+  client_secret         = "secret"
+  description           = "a client used by the destination realm"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+resource "keycloak_realm" "token-exchange_destination_realm" {
+  realm   = "token-exchange_destination_realm"
+  enabled = true
+}
+
+resource keycloak_oidc_identity_provider token-exchange_source_oidc_idp {
+  realm              = keycloak_realm.token-exchange_destination_realm.id
+  alias              = "source"
+  authorization_url  = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/auth"
+  token_url          = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/token"
+  user_info_url      = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/userinfo"
+  jwks_url           = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/certs"
+  validate_signature = true
+  client_id          = keycloak_openid_client.token-exchange_destination_client.client_id
+  client_secret      = keycloak_openid_client.token-exchange_destination_client.client_secret
+  default_scopes     = "openid"
+}
+
+resource "keycloak_openid_client" "token-exchange_webapp_client" {
+  realm_id              = keycloak_realm.token-exchange_destination_realm.id
+  name                  = "webapp_client"
+  client_id             = "webapp_client"
+  client_secret         = "secret"
+  description           = "a webapp client on the destination realm"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+//token exchange feature enabler
+resource "keycloak_identity_provider_token_exchange_scope_permission" "source_oidc_idp_permission" {
+  realm_id       = keycloak_realm.token-exchange_destination_realm.id
+  provider_alias = keycloak_oidc_identity_provider.token-exchange_source_oidc_idp.alias
+  policy_type    = "client"
+  clients        = [keycloak_openid_client.token-exchange_webapp_client.id]
+}
+
diff --git a/keycloak/error.go b/keycloak/error.go
index 23bf7b3..db89f04 100644
--- a/keycloak/error.go
+++ b/keycloak/error.go
@@ -19,3 +19,9 @@ func ErrorIs404(err error) bool {
 
 	return ok && keycloakError != nil && keycloakError.Code == http.StatusNotFound
 }
+
+func ErrorIs409(err error) bool {
+	keycloakError, ok := errwrap.GetType(err, &ApiError{}).(*ApiError)
+
+	return ok && keycloakError != nil && keycloakError.Code == http.StatusConflict
+}
diff --git a/keycloak/identity_provider_permissions.go b/keycloak/identity_provider_permissions.go
new file mode 100644
index 0000000..63458cb
--- /dev/null
+++ b/keycloak/identity_provider_permissions.go
@@ -0,0 +1,46 @@
+package keycloak
+
+import (
+	"fmt"
+)
+
+type IdentityProviderPermissionsInput struct {
+	Enabled bool `json:"enabled"`
+}
+
+type IdentityProviderPermissions struct {
+	RealmId          string                 `json:"-"`
+	ProviderAlias    string                 `json:"-"`
+	Enabled          bool                   `json:"enabled"`
+	Resource         string                 `json:"resource"`
+	ScopePermissions map[string]interface{} `json:"scopePermissions"`
+}
+
+func (keycloakClient *KeycloakClient) EnableIdentityProviderPermissions(realmId, providerAlias string) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/management/permissions", realmId, providerAlias), IdentityProviderPermissionsInput{Enabled: true})
+}
+
+func (keycloakClient *KeycloakClient) DisableIdentityProviderPermissions(realmId, providerAlias string) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/management/permissions", realmId, providerAlias), IdentityProviderPermissionsInput{Enabled: false})
+}
+
+func (keycloakClient *KeycloakClient) GetIdentityProviderPermissions(realmId, providerAlias string) (*IdentityProviderPermissions, error) {
+	var identityProviderPermissions IdentityProviderPermissions
+	identityProviderPermissions.RealmId = realmId
+	identityProviderPermissions.ProviderAlias = providerAlias
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/management/permissions", realmId, providerAlias), &identityProviderPermissions, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return &identityProviderPermissions, nil
+}
+
+func (identityProviderPermissions *IdentityProviderPermissions) GetTokenExchangeScopedPermissionId() (string, error) {
+	if identityProviderPermissions.Enabled {
+		return identityProviderPermissions.ScopePermissions["token-exchange"].(string), nil
+	} else {
+		return "", fmt.Errorf("identity provider permissions are not enabled, thus can not return the linked 'token-exchange' scope based permission")
+	}
+}
diff --git a/provider/provider.go b/provider/provider.go
index cf8d512..b06a1d6 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -19,69 +19,70 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_saml_client_installation_provider":  dataSourceKeycloakSamlClientInstallationProvider(),
 		},
 		ResourcesMap: map[string]*schema.Resource{
-			"keycloak_realm":                                           resourceKeycloakRealm(),
-			"keycloak_realm_events":                                    resourceKeycloakRealmEvents(),
-			"keycloak_required_action":                                 resourceKeycloakRequiredAction(),
-			"keycloak_group":                                           resourceKeycloakGroup(),
-			"keycloak_group_memberships":                               resourceKeycloakGroupMemberships(),
-			"keycloak_default_groups":                                  resourceKeycloakDefaultGroups(),
-			"keycloak_group_roles":                                     resourceKeycloakGroupRoles(),
-			"keycloak_user":                                            resourceKeycloakUser(),
-			"keycloak_user_roles":                                      resourceKeycloakUserRoles(),
-			"keycloak_openid_client":                                   resourceKeycloakOpenidClient(),
-			"keycloak_openid_client_scope":                             resourceKeycloakOpenidClientScope(),
-			"keycloak_ldap_user_federation":                            resourceKeycloakLdapUserFederation(),
-			"keycloak_ldap_user_attribute_mapper":                      resourceKeycloakLdapUserAttributeMapper(),
-			"keycloak_ldap_group_mapper":                               resourceKeycloakLdapGroupMapper(),
-			"keycloak_ldap_role_mapper":                                resourceKeycloakLdapRoleMapper(),
-			"keycloak_ldap_hardcoded_role_mapper":                      resourceKeycloakLdapHardcodedRoleMapper(),
-			"keycloak_ldap_hardcoded_group_mapper":                     resourceKeycloakLdapHardcodedGroupMapper(),
-			"keycloak_ldap_msad_user_account_control_mapper":           resourceKeycloakLdapMsadUserAccountControlMapper(),
-			"keycloak_ldap_msad_lds_user_account_control_mapper":       resourceKeycloakLdapMsadLdsUserAccountControlMapper(),
-			"keycloak_ldap_full_name_mapper":                           resourceKeycloakLdapFullNameMapper(),
-			"keycloak_custom_user_federation":                          resourceKeycloakCustomUserFederation(),
-			"keycloak_openid_user_attribute_protocol_mapper":           resourceKeycloakOpenIdUserAttributeProtocolMapper(),
-			"keycloak_openid_user_property_protocol_mapper":            resourceKeycloakOpenIdUserPropertyProtocolMapper(),
-			"keycloak_openid_group_membership_protocol_mapper":         resourceKeycloakOpenIdGroupMembershipProtocolMapper(),
-			"keycloak_openid_full_name_protocol_mapper":                resourceKeycloakOpenIdFullNameProtocolMapper(),
-			"keycloak_openid_hardcoded_claim_protocol_mapper":          resourceKeycloakOpenIdHardcodedClaimProtocolMapper(),
-			"keycloak_openid_audience_protocol_mapper":                 resourceKeycloakOpenIdAudienceProtocolMapper(),
-			"keycloak_openid_hardcoded_role_protocol_mapper":           resourceKeycloakOpenIdHardcodedRoleProtocolMapper(),
-			"keycloak_openid_user_realm_role_protocol_mapper":          resourceKeycloakOpenIdUserRealmRoleProtocolMapper(),
-			"keycloak_openid_user_client_role_protocol_mapper":         resourceKeycloakOpenIdUserClientRoleProtocolMapper(),
-			"keycloak_openid_user_session_note_protocol_mapper":        resourceKeycloakOpenIdUserSessionNoteProtocolMapper(),
-			"keycloak_openid_client_default_scopes":                    resourceKeycloakOpenidClientDefaultScopes(),
-			"keycloak_openid_client_optional_scopes":                   resourceKeycloakOpenidClientOptionalScopes(),
-			"keycloak_saml_client":                                     resourceKeycloakSamlClient(),
-			"keycloak_generic_client_protocol_mapper":                  resourceKeycloakGenericClientProtocolMapper(),
-			"keycloak_generic_client_role_mapper":                      resourceKeycloakGenericClientRoleMapper(),
-			"keycloak_saml_user_attribute_protocol_mapper":             resourceKeycloakSamlUserAttributeProtocolMapper(),
-			"keycloak_saml_user_property_protocol_mapper":              resourceKeycloakSamlUserPropertyProtocolMapper(),
-			"keycloak_hardcoded_attribute_identity_provider_mapper":    resourceKeycloakHardcodedAttributeIdentityProviderMapper(),
-			"keycloak_hardcoded_role_identity_provider_mapper":         resourceKeycloakHardcodedRoleIdentityProviderMapper(),
-			"keycloak_attribute_importer_identity_provider_mapper":     resourceKeycloakAttributeImporterIdentityProviderMapper(),
-			"keycloak_attribute_to_role_identity_provider_mapper":      resourceKeycloakAttributeToRoleIdentityProviderMapper(),
-			"keycloak_user_template_importer_identity_provider_mapper": resourceKeycloakUserTemplateImporterIdentityProviderMapper(),
-			"keycloak_saml_identity_provider":                          resourceKeycloakSamlIdentityProvider(),
-			"keycloak_oidc_google_identity_provider":                   resourceKeycloakOidcGoogleIdentityProvider(),
-			"keycloak_oidc_identity_provider":                          resourceKeycloakOidcIdentityProvider(),
-			"keycloak_openid_client_authorization_resource":            resourceKeycloakOpenidClientAuthorizationResource(),
-			"keycloak_openid_client_group_policy":                      resourceKeycloakOpenidClientAuthorizationGroupPolicy(),
-			"keycloak_openid_client_role_policy":                       resourceKeycloakOpenidClientAuthorizationRolePolicy(),
-			"keycloak_openid_client_aggregate_policy":                  resourceKeycloakOpenidClientAuthorizationAggregatePolicy(),
-			"keycloak_openid_client_js_policy":                         resourceKeycloakOpenidClientAuthorizationJSPolicy(),
-			"keycloak_openid_client_time_policy":                       resourceKeycloakOpenidClientAuthorizationTimePolicy(),
-			"keycloak_openid_client_user_policy":                       resourceKeycloakOpenidClientAuthorizationUserPolicy(),
-			"keycloak_openid_client_client_policy":                     resourceKeycloakOpenidClientAuthorizationClientPolicy(),
-			"keycloak_openid_client_authorization_scope":               resourceKeycloakOpenidClientAuthorizationScope(),
-			"keycloak_openid_client_authorization_permission":          resourceKeycloakOpenidClientAuthorizationPermission(),
-			"keycloak_openid_client_service_account_role":              resourceKeycloakOpenidClientServiceAccountRole(),
-			"keycloak_openid_client_service_account_realm_role":        resourceKeycloakOpenidClientServiceAccountRealmRole(),
-			"keycloak_role":                                            resourceKeycloakRole(),
-			"keycloak_authentication_flow":                             resourceKeycloakAuthenticationFlow(),
-			"keycloak_authentication_subflow":                          resourceKeycloakAuthenticationSubFlow(),
-			"keycloak_authentication_execution":                        resourceKeycloakAuthenticationExecution(),
-			"keycloak_authentication_execution_config":                 resourceKeycloakAuthenticationExecutionConfig(),
+			"keycloak_realm":                                             resourceKeycloakRealm(),
+			"keycloak_realm_events":                                      resourceKeycloakRealmEvents(),
+			"keycloak_required_action":                                   resourceKeycloakRequiredAction(),
+			"keycloak_group":                                             resourceKeycloakGroup(),
+			"keycloak_group_memberships":                                 resourceKeycloakGroupMemberships(),
+			"keycloak_default_groups":                                    resourceKeycloakDefaultGroups(),
+			"keycloak_group_roles":                                       resourceKeycloakGroupRoles(),
+			"keycloak_user":                                              resourceKeycloakUser(),
+			"keycloak_user_roles":                                        resourceKeycloakUserRoles(),
+			"keycloak_openid_client":                                     resourceKeycloakOpenidClient(),
+			"keycloak_openid_client_scope":                               resourceKeycloakOpenidClientScope(),
+			"keycloak_ldap_user_federation":                              resourceKeycloakLdapUserFederation(),
+			"keycloak_ldap_user_attribute_mapper":                        resourceKeycloakLdapUserAttributeMapper(),
+			"keycloak_ldap_group_mapper":                                 resourceKeycloakLdapGroupMapper(),
+			"keycloak_ldap_role_mapper":                                  resourceKeycloakLdapRoleMapper(),
+			"keycloak_ldap_hardcoded_role_mapper":                        resourceKeycloakLdapHardcodedRoleMapper(),
+			"keycloak_ldap_hardcoded_group_mapper":                       resourceKeycloakLdapHardcodedGroupMapper(),
+			"keycloak_ldap_msad_user_account_control_mapper":             resourceKeycloakLdapMsadUserAccountControlMapper(),
+			"keycloak_ldap_msad_lds_user_account_control_mapper":         resourceKeycloakLdapMsadLdsUserAccountControlMapper(),
+			"keycloak_ldap_full_name_mapper":                             resourceKeycloakLdapFullNameMapper(),
+			"keycloak_custom_user_federation":                            resourceKeycloakCustomUserFederation(),
+			"keycloak_openid_user_attribute_protocol_mapper":             resourceKeycloakOpenIdUserAttributeProtocolMapper(),
+			"keycloak_openid_user_property_protocol_mapper":              resourceKeycloakOpenIdUserPropertyProtocolMapper(),
+			"keycloak_openid_group_membership_protocol_mapper":           resourceKeycloakOpenIdGroupMembershipProtocolMapper(),
+			"keycloak_openid_full_name_protocol_mapper":                  resourceKeycloakOpenIdFullNameProtocolMapper(),
+			"keycloak_openid_hardcoded_claim_protocol_mapper":            resourceKeycloakOpenIdHardcodedClaimProtocolMapper(),
+			"keycloak_openid_audience_protocol_mapper":                   resourceKeycloakOpenIdAudienceProtocolMapper(),
+			"keycloak_openid_hardcoded_role_protocol_mapper":             resourceKeycloakOpenIdHardcodedRoleProtocolMapper(),
+			"keycloak_openid_user_realm_role_protocol_mapper":            resourceKeycloakOpenIdUserRealmRoleProtocolMapper(),
+			"keycloak_openid_user_client_role_protocol_mapper":           resourceKeycloakOpenIdUserClientRoleProtocolMapper(),
+			"keycloak_openid_user_session_note_protocol_mapper":          resourceKeycloakOpenIdUserSessionNoteProtocolMapper(),
+			"keycloak_openid_client_default_scopes":                      resourceKeycloakOpenidClientDefaultScopes(),
+			"keycloak_openid_client_optional_scopes":                     resourceKeycloakOpenidClientOptionalScopes(),
+			"keycloak_saml_client":                                       resourceKeycloakSamlClient(),
+			"keycloak_generic_client_protocol_mapper":                    resourceKeycloakGenericClientProtocolMapper(),
+			"keycloak_generic_client_role_mapper":                        resourceKeycloakGenericClientRoleMapper(),
+			"keycloak_saml_user_attribute_protocol_mapper":               resourceKeycloakSamlUserAttributeProtocolMapper(),
+			"keycloak_saml_user_property_protocol_mapper":                resourceKeycloakSamlUserPropertyProtocolMapper(),
+			"keycloak_hardcoded_attribute_identity_provider_mapper":      resourceKeycloakHardcodedAttributeIdentityProviderMapper(),
+			"keycloak_hardcoded_role_identity_provider_mapper":           resourceKeycloakHardcodedRoleIdentityProviderMapper(),
+			"keycloak_attribute_importer_identity_provider_mapper":       resourceKeycloakAttributeImporterIdentityProviderMapper(),
+			"keycloak_attribute_to_role_identity_provider_mapper":        resourceKeycloakAttributeToRoleIdentityProviderMapper(),
+			"keycloak_user_template_importer_identity_provider_mapper":   resourceKeycloakUserTemplateImporterIdentityProviderMapper(),
+			"keycloak_saml_identity_provider":                            resourceKeycloakSamlIdentityProvider(),
+			"keycloak_oidc_google_identity_provider":                     resourceKeycloakOidcGoogleIdentityProvider(),
+			"keycloak_oidc_identity_provider":                            resourceKeycloakOidcIdentityProvider(),
+			"keycloak_openid_client_authorization_resource":              resourceKeycloakOpenidClientAuthorizationResource(),
+			"keycloak_openid_client_group_policy":                        resourceKeycloakOpenidClientAuthorizationGroupPolicy(),
+			"keycloak_openid_client_role_policy":                         resourceKeycloakOpenidClientAuthorizationRolePolicy(),
+			"keycloak_openid_client_aggregate_policy":                    resourceKeycloakOpenidClientAuthorizationAggregatePolicy(),
+			"keycloak_openid_client_js_policy":                           resourceKeycloakOpenidClientAuthorizationJSPolicy(),
+			"keycloak_openid_client_time_policy":                         resourceKeycloakOpenidClientAuthorizationTimePolicy(),
+			"keycloak_openid_client_user_policy":                         resourceKeycloakOpenidClientAuthorizationUserPolicy(),
+			"keycloak_openid_client_client_policy":                       resourceKeycloakOpenidClientAuthorizationClientPolicy(),
+			"keycloak_openid_client_authorization_scope":                 resourceKeycloakOpenidClientAuthorizationScope(),
+			"keycloak_openid_client_authorization_permission":            resourceKeycloakOpenidClientAuthorizationPermission(),
+			"keycloak_openid_client_service_account_role":                resourceKeycloakOpenidClientServiceAccountRole(),
+			"keycloak_openid_client_service_account_realm_role":          resourceKeycloakOpenidClientServiceAccountRealmRole(),
+			"keycloak_role":                                              resourceKeycloakRole(),
+			"keycloak_authentication_flow":                               resourceKeycloakAuthenticationFlow(),
+			"keycloak_authentication_subflow":                            resourceKeycloakAuthenticationSubFlow(),
+			"keycloak_authentication_execution":                          resourceKeycloakAuthenticationExecution(),
+			"keycloak_authentication_execution_config":                   resourceKeycloakAuthenticationExecutionConfig(),
+			"keycloak_identity_provider_token_exchange_scope_permission": resourceKeycloakIdentityProviderTokenExchangeScopePermission(),
 		},
 		Schema: map[string]*schema.Schema{
 			"client_id": {
diff --git a/provider/resource_keycloak_identity_provider_token_exchange_scope_permission.go b/provider/resource_keycloak_identity_provider_token_exchange_scope_permission.go
new file mode 100644
index 0000000..781d203
--- /dev/null
+++ b/provider/resource_keycloak_identity_provider_token_exchange_scope_permission.go
@@ -0,0 +1,297 @@
+package provider
+
+import (
+	"encoding/hex"
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"log"
+	"math/rand"
+	"strings"
+)
+
+var (
+	keycloakIdpTokenExchangePermissionPolicyTypes = []string{"client"}
+)
+
+func resourceKeycloakIdentityProviderTokenExchangeScopePermission() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakIdentityProviderTokenExchangeScopePermissionCreate,
+		Read:   resourceKeycloakIdentityProviderTokenExchangeScopePermissionRead,
+		Delete: resourceKeycloakIdentityProviderTokenExchangeScopePermissionDelete,
+		Update: resourceKeycloakIdentityProviderTokenExchangeScopePermissionUpdate,
+		// This resource can be imported using {{realmId}}/{{providerAlias}}. The provider alias is displayed in the URL when editing it from the GUI
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakIdentityProviderTokenExchangeScopePermissionImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"provider_alias": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"policy_type": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Default:      "client",
+				Description:  "Type of policy that is created. At the moment only 'client' type is supported",
+				ValidateFunc: validation.StringInSlice(keycloakIdpTokenExchangePermissionPolicyTypes, false),
+			},
+			"clients": {
+				Type:        schema.TypeSet,
+				Elem:        &schema.Schema{Type: schema.TypeString},
+				Required:    true,
+				Description: "Ids of the clients for which a policy will be created and set on scope based token exchange permission",
+			},
+			"policy_id": {
+				Type:        schema.TypeString,
+				Computed:    true,
+				Description: "Policy id that will be set on the scope based token exchange permission automatically created by enabling permissions on the reference identity provider",
+			},
+			"authorization_resource_server_id": {
+				Type:        schema.TypeString,
+				Computed:    true,
+				Description: "Resource server id representing the realm management client on which this permission is managed",
+			},
+			"authorization_idp_resource_id": {
+				Type:        schema.TypeString,
+				Computed:    true,
+				Description: "Resource id representing the identity provider, this automatically created by keycloak",
+			},
+			"authorization_token_exchange_scope_permission_id": {
+				Type:        schema.TypeString,
+				Computed:    true,
+				Description: "Permission id representing the Permission with scope 'Token Exchange' and the resource 'authorization_idp_resource_id', this automatically created by keycloak, the policy id will be set on this permission",
+			},
+		},
+	}
+}
+
+func setIdentityProviderTokenExchangeScopePermissionClientPolicy(keycloakClient *keycloak.KeycloakClient, realmId, providerAlias string, clients []string) error {
+	identityProviderPermissions, err := keycloakClient.GetIdentityProviderPermissions(realmId, providerAlias)
+	if err != nil {
+		return err
+	}
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	tokenExchangeScopedPermissionId, err := identityProviderPermissions.GetTokenExchangeScopedPermissionId()
+	if err != nil {
+		return err
+	}
+
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, tokenExchangeScopedPermissionId)
+	if err != nil {
+		return err
+	}
+
+	if len(permission.Policies) == 0 {
+		policyId, err := createClientPolicy(keycloakClient, realmId, realmManagementClient.Id, providerAlias, clients)
+		if err != nil {
+			return err
+		}
+		permission.Policies = []string{policyId}
+		return keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
+
+	} else if len(permission.Policies) == 1 {
+		openidClientAuthorizationClientPolicy, err := keycloakClient.GetOpenidClientAuthorizationClientPolicy(realmId, realmManagementClient.Id, permission.Policies[0])
+		if err != nil {
+			return err
+		}
+		openidClientAuthorizationClientPolicy.Clients = clients
+		return keycloakClient.UpdateOpenidClientAuthorizationClientPolicy(openidClientAuthorizationClientPolicy)
+
+	} else {
+		return fmt.Errorf("only one client policy is supported, but %d were found", len(permission.Policies))
+	}
+}
+
+func createClientPolicy(keycloakClient *keycloak.KeycloakClient, realmId, realmManagementClientId, providerAlias string, clients []string) (string, error) {
+	openidClientAuthorizationClientPolicy := &keycloak.OpenidClientAuthorizationClientPolicy{
+		RealmId:          realmId,
+		ResourceServerId: realmManagementClientId,
+		Name:             providerAlias + "_idp_client_policy",
+		DecisionStrategy: "UNANIMOUS",
+		Logic:            "POSITIVE",
+		Type:             "client",
+		Clients:          clients,
+	}
+	err := keycloakClient.NewOpenidClientAuthorizationClientPolicy(openidClientAuthorizationClientPolicy)
+	if err != nil {
+		if keycloak.ErrorIs409(err) {
+			b := make([]byte, 4)
+			rand.Read(b)
+			suffix := hex.EncodeToString(b)
+			openidClientAuthorizationClientPolicy.Name = providerAlias + "_" + suffix + "_idp_client_policy"
+			err = keycloakClient.NewOpenidClientAuthorizationClientPolicy(openidClientAuthorizationClientPolicy)
+		}
+	}
+	if err != nil {
+		return "", err
+	}
+	return openidClientAuthorizationClientPolicy.Id, nil
+}
+
+func unsetIdentityProviderTokenExchangeScopePermissionPolicy(keycloakClient *keycloak.KeycloakClient, realmId, providerAlias, policyId string) error {
+	identityProviderPermissions, err := keycloakClient.GetIdentityProviderPermissions(realmId, providerAlias)
+	if err != nil {
+		return err
+	}
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	tokenExchangeScopedPermissionId, err := identityProviderPermissions.GetTokenExchangeScopedPermissionId()
+	if err != nil {
+		return err
+	}
+
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, tokenExchangeScopedPermissionId)
+	if err != nil {
+		return err
+	}
+
+	permission.Policies = []string{}
+	err = keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.DisableIdentityProviderPermissions(realmId, providerAlias)
+	if err != nil {
+		return err
+	}
+
+	_ = keycloakClient.DeleteOpenidClientAuthorizationClientPolicy(realmId, realmManagementClient.Id, policyId)
+
+	return nil
+}
+
+func resourceKeycloakIdentityProviderTokenExchangeScopePermissionCreate(data *schema.ResourceData, meta interface{}) error {
+	return resourceKeycloakIdentityProviderTokenExchangeScopePermissionUpdate(data, meta)
+}
+
+func resourceKeycloakIdentityProviderTokenExchangeScopePermissionUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+	realmId := data.Get("realm_id").(string)
+	providerAlias := data.Get("provider_alias").(string)
+	policyType := data.Get("policy_type").(string)
+	var clients []string
+
+	if v, ok := data.GetOk("clients"); ok {
+		for _, client := range v.(*schema.Set).List() {
+			clients = append(clients, client.(string))
+		}
+	}
+
+	err := keycloakClient.EnableIdentityProviderPermissions(realmId, providerAlias)
+	if err != nil {
+		return err
+	}
+	if policyType == "client" {
+		err = setIdentityProviderTokenExchangeScopePermissionClientPolicy(keycloakClient, realmId, providerAlias, clients)
+		if err != nil {
+			return err
+		}
+	} else {
+		return fmt.Errorf("invalid policy type, supported types are ['client']")
+	}
+	return resourceKeycloakIdentityProviderTokenExchangeScopePermissionRead(data, meta)
+}
+
+func resourceKeycloakIdentityProviderTokenExchangeScopePermissionRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+	realmId := data.Get("realm_id").(string)
+	providerAlias := data.Get("provider_alias").(string)
+
+	identityProviderPermissions, err := keycloakClient.GetIdentityProviderPermissions(realmId, providerAlias)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+	if !identityProviderPermissions.Enabled {
+		log.Printf("[WARN] Removing resource with id %s from state as it no longer enabled", data.Id())
+		data.SetId("")
+		return nil
+	}
+
+	data.SetId(identityProviderPermissions.RealmId + "/" + identityProviderPermissions.ProviderAlias)
+	data.Set("realm_id", identityProviderPermissions.RealmId)
+	data.Set("provider_alias", identityProviderPermissions.ProviderAlias)
+
+	realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
+	if err != nil {
+		return err
+	}
+
+	tokenExchangeScopedPermissionId, err := identityProviderPermissions.GetTokenExchangeScopedPermissionId()
+	if err != nil {
+		return err
+	}
+
+	permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, tokenExchangeScopedPermissionId)
+	if err != nil {
+		return err
+	}
+
+	var openidClientAuthorizationClientPolicyId string
+	if len(permission.Policies) >= 1 {
+		openidClientAuthorizationClientPolicyId = permission.Policies[0]
+	} else {
+		openidClientAuthorizationClientPolicyId, err = createClientPolicy(keycloakClient, realmId, realmManagementClient.Id, providerAlias, data.Get("clients").([]string))
+		if err != nil {
+			return err
+		}
+	}
+	openidClientAuthorizationClientPolicy, err := keycloakClient.GetOpenidClientAuthorizationClientPolicy(realmId, realmManagementClient.Id, openidClientAuthorizationClientPolicyId)
+	if err != nil {
+		return err
+	}
+
+	data.Set("policy_id", openidClientAuthorizationClientPolicy.Id)
+	data.Set("clients", openidClientAuthorizationClientPolicy.Clients)
+
+	data.Set("policy_type", data.Get("policy_type"))
+	data.Set("authorization_resource_server_id", realmManagementClient.Id)
+	data.Set("authorization_idp_resource_id", identityProviderPermissions.Resource)
+
+	data.Set("authorization_token_exchange_scope_permission_id", tokenExchangeScopedPermissionId)
+
+	return nil
+}
+
+func resourceKeycloakIdentityProviderTokenExchangeScopePermissionDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	providerAlias := data.Get("provider_alias").(string)
+	policyId := data.Get("policy_id").(string)
+
+	identityProviderPermissions, err := keycloakClient.GetIdentityProviderPermissions(realmId, providerAlias)
+	if err == nil && identityProviderPermissions.Enabled {
+		_ = unsetIdentityProviderTokenExchangeScopePermissionPolicy(keycloakClient, realmId, providerAlias, policyId)
+	}
+	return keycloakClient.DisableIdentityProviderPermissions(realmId, providerAlias)
+}
+
+func resourceKeycloakIdentityProviderTokenExchangeScopePermissionImport(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}}/{{providerAlias}}")
+	}
+	d.SetId(parts[0] + "/" + parts[1])
+	d.Set("realm_id", parts[0])
+	d.Set("provider_alias", parts[1])
+	return []*schema.ResourceData{d}, nil
+}


Test patch:
diff --git a/provider/resource_keycloak_identity_provider_token_exchange_scope_permission_test.go b/provider/resource_keycloak_identity_provider_token_exchange_scope_permission_test.go
new file mode 100644
index 0000000..4597f57
--- /dev/null
+++ b/provider/resource_keycloak_identity_provider_token_exchange_scope_permission_test.go
@@ -0,0 +1,447 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"regexp"
+	"testing"
+)
+
+func TestAccKeycloakIdpTokenExchangeScopePermission_basic(t *testing.T) {
+	realmName := "tf_token_exchange-" + acctest.RandString(10)
+	providerAlias := "tf-" + acctest.RandString(10)
+	webappClientId := "tf-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakIdpTokenExchangeScopePermissionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId),
+				Check:  testAccCheckKeycloakIdpTokenExchangeScopePermissionExists("keycloak_identity_provider_token_exchange_scope_permission.my_permission"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakIdpTokenExchangeScopePermission_createAfterManualDestroy(t *testing.T) {
+	var idpPermissions = &keycloak.IdentityProviderPermissions{}
+
+	realmName := "tf_token_exchange-" + acctest.RandString(10)
+	providerAlias := "tf-" + acctest.RandString(10)
+	webappClientId := "tf-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakIdpTokenExchangeScopePermissionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakIdpTokenExchangeScopePermissionExists("keycloak_identity_provider_token_exchange_scope_permission.my_permission"),
+					testAccCheckKeycloakIdpPermissionFetch("keycloak_identity_provider_token_exchange_scope_permission.my_permission", idpPermissions),
+				),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+					err := keycloakClient.DisableIdentityProviderPermissions(idpPermissions.RealmId, idpPermissions.ProviderAlias)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId),
+				Check:  testAccCheckKeycloakIdpTokenExchangeScopePermissionExists("keycloak_identity_provider_token_exchange_scope_permission.my_permission"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakIdpTokenExchangeScopePermission_import(t *testing.T) {
+	realmName := "tf_token_exchange-" + acctest.RandString(10)
+	providerAlias := "tf-" + acctest.RandString(10)
+	webappClientId := "tf-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakIdpTokenExchangeScopePermissionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId),
+				Check:  testAccCheckKeycloakIdpTokenExchangeScopePermissionExists("keycloak_identity_provider_token_exchange_scope_permission.my_permission"),
+			},
+			{
+				ResourceName:            "keycloak_identity_provider_token_exchange_scope_permission.my_permission",
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"policy_type"},
+			},
+		},
+	})
+}
+
+func TestAccKeycloakIdpTokenExchangeScopePermission_updatePolicyMultipleClients(t *testing.T) {
+	realmName := "tf_token_exchange-" + acctest.RandString(10)
+	providerAlias := "tf-" + acctest.RandString(10)
+	webappClientId := "tf-" + acctest.RandString(10)
+	webappClientId2 := "tf-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakIdpTokenExchangeScopePermissionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId),
+				Check:  testAccCheckKeycloakIdpTokenExchangeScopePermissionClientPolicyHasClient("keycloak_identity_provider_token_exchange_scope_permission.my_permission", webappClientId),
+			},
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_multipleClients(realmName, providerAlias, webappClientId, webappClientId2),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakIdpTokenExchangeScopePermissionClientPolicyHasClient("keycloak_identity_provider_token_exchange_scope_permission.my_permission", webappClientId),
+					testAccCheckKeycloakIdpTokenExchangeScopePermissionClientPolicyHasClient("keycloak_identity_provider_token_exchange_scope_permission.my_permission", webappClientId2),
+				),
+			},
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId2),
+				Check:  testAccCheckKeycloakIdpTokenExchangeScopePermissionClientPolicyHasClient("keycloak_identity_provider_token_exchange_scope_permission.my_permission", webappClientId2),
+			},
+			{
+				Config: testKeycloakIdpTokenExchangeScopePermission_basic(realmName, providerAlias, webappClientId),
+				Check:  testAccCheckKeycloakIdpTokenExchangeScopePermissionClientPolicyHasClient("keycloak_identity_provider_token_exchange_scope_permission.my_permission", webappClientId),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakIdpTokenExchangeScopePermission_rolePolicy(t *testing.T) {
+	realmName := "tf_token_exchange-" + acctest.RandString(10)
+	providerAlias := "tf-" + acctest.RandString(10)
+	webappClientId := "tf-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakIdpTokenExchangeScopePermissionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakIdpTokenExchangeScopePermission_rolePolicy(realmName, providerAlias, webappClientId),
+				ExpectError: regexp.MustCompile(".*expected policy_type to be one of.*"),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakIdpTokenExchangeScopePermissionDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_identity_provider_token_exchange_scope_permission" {
+				continue
+			}
+
+			realmId := rs.Primary.Attributes["realm_id"]
+			providerAlias := rs.Primary.Attributes["provider_alias"]
+			policyId := rs.Primary.Attributes["policy_id"]
+			authorizationResourceServerId := rs.Primary.Attributes["authorization_resource_server_id"]
+			authorizationIdpResourceId := rs.Primary.Attributes["authorization_idp_resource_id"]
+			authorizationTokenExchangeScopePermissionId := rs.Primary.Attributes["authorization_token_exchange_scope_permission_id"]
+
+			keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+			permissions, _ := keycloakClient.GetIdentityProviderPermissions(realmId, providerAlias)
+			if permissions != nil {
+				return fmt.Errorf("idp permissions for realm id %s and provider alias %s still exists", realmId, providerAlias)
+			}
+
+			tokenExchangePermission, _ := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, authorizationResourceServerId, authorizationTokenExchangeScopePermissionId)
+			if tokenExchangePermission != nil {
+				return fmt.Errorf("tokenExchangePermission for realm id %s, resource server id %s and permission id %s still exists", realmId, authorizationResourceServerId, authorizationTokenExchangeScopePermissionId)
+			}
+
+			idpResource, _ := keycloakClient.GetOpenidClientAuthorizationResource(realmId, authorizationResourceServerId, authorizationIdpResourceId)
+			if idpResource != nil {
+				return fmt.Errorf("idp resource for realm id%s, resource server id %s and resource id %s still exists", realmId, authorizationResourceServerId, authorizationIdpResourceId)
+			}
+
+			policy, _ := keycloakClient.GetOpenidClientAuthorizationClientPolicy(realmId, authorizationResourceServerId, policyId)
+			if policy != nil {
+				return fmt.Errorf("client policy for realm id %s, resource server id %s and policy id %s still exists", realmId, authorizationResourceServerId, policyId)
+			}
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakIdpTokenExchangeScopePermissionExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		permissions, err := getIdpPermissionsFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		rs, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return fmt.Errorf("resource not found: %s", resourceName)
+		}
+		policyId := rs.Primary.Attributes["policy_id"]
+		authorizationResourceServerId := rs.Primary.Attributes["authorization_resource_server_id"]
+		authorizationIdpResourceId := rs.Primary.Attributes["authorization_idp_resource_id"]
+		authorizationTokenExchangeScopePermissionId := rs.Primary.Attributes["authorization_token_exchange_scope_permission_id"]
+
+		var realmManagementId string
+		clients, _ := keycloakClient.GetOpenidClients(permissions.RealmId, false)
+		for _, client := range clients {
+			if client.ClientId == "realm-management" {
+				realmManagementId = client.Id
+				break
+			}
+		}
+
+		if authorizationResourceServerId != realmManagementId {
+			return fmt.Errorf("computed authorizationResourceServerId %s was not equal to %s (the id of the realm-management client)", authorizationResourceServerId, realmManagementId)
+		}
+
+		tokenExchangeScopedPermissionId, err := permissions.GetTokenExchangeScopedPermissionId()
+		if err != nil {
+			return err
+		}
+
+		if authorizationTokenExchangeScopePermissionId != tokenExchangeScopedPermissionId {
+			return fmt.Errorf("computed authorizationTokenExchangeScopePermissionId %s was not equal to %s scope permission id set on the idp permission", authorizationTokenExchangeScopePermissionId, tokenExchangeScopedPermissionId)
+		}
+
+		tokenExchangeScopedPermission, err := keycloakClient.GetOpenidClientAuthorizationPermission(permissions.RealmId, realmManagementId, tokenExchangeScopedPermissionId)
+		if err != nil {
+			return err
+		}
+
+		if tokenExchangeScopedPermission == nil {
+			return fmt.Errorf("token exchange scope permission represented to idp permission could not be found")
+		}
+
+		if len(tokenExchangeScopedPermission.Policies) != 1 {
+			return fmt.Errorf("token exchange scope permission has not exact 1 policy, it has %d", len(tokenExchangeScopedPermission.Policies))
+		}
+
+		policy, err := keycloakClient.GetOpenidClientAuthorizationClientPolicy(permissions.RealmId, realmManagementId, tokenExchangeScopedPermission.Policies[0])
+		if err != nil {
+			return err
+		}
+
+		if policyId != policy.Id {
+			return fmt.Errorf("computed policyId %s was not equal to %s policyId found on the token exchange scope based permission", policyId, policy.Id)
+		}
+
+		idpResource, err := keycloakClient.GetOpenidClientAuthorizationResource(permissions.RealmId, realmManagementId, permissions.Resource)
+		if err != nil {
+			return err
+		}
+
+		if tokenExchangeScopedPermission.Resources[0] != idpResource.Id {
+			return fmt.Errorf("fetched permission resources %s do not correspond with the idp resource provided id %s", tokenExchangeScopedPermission.Resources[0], idpResource.Id)
+		}
+
+		if authorizationIdpResourceId != idpResource.Id {
+			return fmt.Errorf("computed authorizationIdpResourceId %s was not equal to %s sidp resource id found on the token exchange scope based permission", authorizationIdpResourceId, idpResource.Id)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakIdpTokenExchangeScopePermissionClientPolicyHasClient(resourceName, clientId string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return fmt.Errorf("resource not found: %s", resourceName)
+		}
+		realmId := rs.Primary.Attributes["realm_id"]
+		authorizationResourceServerId := rs.Primary.Attributes["authorization_resource_server_id"]
+		policyId := rs.Primary.Attributes["policy_id"]
+
+		policy, err := keycloakClient.GetOpenidClientAuthorizationClientPolicy(realmId, authorizationResourceServerId, policyId)
+		if err != nil {
+			return err
+		}
+
+		client, err := keycloakClient.GetOpenidClientByClientId(realmId, clientId)
+		if err != nil {
+			return err
+		}
+
+		clientNotFound := true
+		for _, idOfClientString := range policy.Clients {
+			if idOfClientString == client.Id {
+				clientNotFound = false
+				break
+			}
+		}
+		if clientNotFound {
+			return fmt.Errorf("client with clientId %s was not linked to policy", clientId)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakIdpPermissionFetch(resourceName string, idpPermissions *keycloak.IdentityProviderPermissions) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedPermissions, err := getIdpPermissionsFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		idpPermissions.RealmId = fetchedPermissions.RealmId
+		idpPermissions.Enabled = fetchedPermissions.Enabled
+		idpPermissions.ProviderAlias = fetchedPermissions.ProviderAlias
+		idpPermissions.ScopePermissions = fetchedPermissions.ScopePermissions
+		idpPermissions.Resource = fetchedPermissions.Resource
+
+		return nil
+	}
+}
+
+func getIdpPermissionsFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProviderPermissions, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	realmId := rs.Primary.Attributes["realm_id"]
+	providerAlias := rs.Primary.Attributes["provider_alias"]
+
+	permissions, err := keycloakClient.GetIdentityProviderPermissions(realmId, providerAlias)
+	if err != nil {
+		return nil, fmt.Errorf("error getting idp permissions with realm id %s and provider alias %s: %s", realmId, providerAlias, err)
+
+	}
+	return permissions, nil
+}
+
+func testKeycloakIdpTokenExchangeScopePermission_basic(realmId, providerAlias, webappClientId string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "my_idp" {
+  realm              = keycloak_realm.realm.id
+  alias              = "%s"
+  authorization_url  = "http://localhost:8080/auth/realms/something/protocol/openid-connect/auth"
+  token_url          = "http://localhost:8080/auth/realms/something/protocol/openid-connect/token"
+  client_id          = "clientid"
+  client_secret      = "secret"
+}
+
+resource "keycloak_openid_client" "webapp_client" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "webapp_client"
+  client_id             = "%s"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+resource "keycloak_identity_provider_token_exchange_scope_permission" "my_permission" {
+  realm_id       = keycloak_realm.realm.id
+  provider_alias = keycloak_oidc_identity_provider.my_idp.alias
+  policy_type    = "client"
+  clients        = [keycloak_openid_client.webapp_client.id]
+}
+	`, realmId, providerAlias, webappClientId)
+}
+
+func testKeycloakIdpTokenExchangeScopePermission_multipleClients(realmId, providerAlias, webappClientId, webappClientId2 string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "my_idp" {
+  realm              = keycloak_realm.realm.id
+  alias              = "%s"
+  authorization_url  = "http://localhost:8080/auth/realms/something/protocol/openid-connect/auth"
+  token_url          = "http://localhost:8080/auth/realms/something/protocol/openid-connect/token"
+  client_id          = "clientid"
+  client_secret      = "secret"
+}
+
+resource "keycloak_openid_client" "webapp_client" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "webapp_client"
+  client_id             = "%s"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+resource "keycloak_openid_client" "webapp_client2" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "webapp_client"
+  client_id             = "%s"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+resource "keycloak_identity_provider_token_exchange_scope_permission" "my_permission" {
+  realm_id       = keycloak_realm.realm.id
+  provider_alias = keycloak_oidc_identity_provider.my_idp.alias
+  policy_type    = "client"
+  clients        = [keycloak_openid_client.webapp_client.id, keycloak_openid_client.webapp_client2.id]
+}
+	`, realmId, providerAlias, webappClientId, webappClientId2)
+}
+
+func testKeycloakIdpTokenExchangeScopePermission_rolePolicy(realmId, providerAlias, webappClientId string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_oidc_identity_provider" "my_idp" {
+  realm              = keycloak_realm.realm.id
+  alias              = "%s"
+  authorization_url  = "http://localhost:8080/auth/realms/something/protocol/openid-connect/auth"
+  token_url          = "http://localhost:8080/auth/realms/something/protocol/openid-connect/token"
+  client_id          = "clientid"
+  client_secret      = "secret"
+}
+
+resource "keycloak_openid_client" "webapp_client" {
+  realm_id              = keycloak_realm.realm.id
+  name                  = "webapp_client"
+  client_id             = "%s"
+  client_secret         = "secret"
+  access_type           = "CONFIDENTIAL"
+  standard_flow_enabled = true
+  valid_redirect_uris = [
+    "http://localhost:8080/*",
+  ]
+}
+
+resource "keycloak_identity_provider_token_exchange_scope_permission" "my_permission" {
+  realm_id       = keycloak_realm.realm.id
+  provider_alias = keycloak_oidc_identity_provider.my_idp.alias
+  policy_type    = "role"
+  clients        = [keycloak_openid_client.webapp_client.id]
+}
+	`, realmId, providerAlias, webappClientId)
+}