Repository: mrparkers/terraform-provider-keycloak
Pull request #97 (2019-02-26 19:54:32)

new resource: keycloak_openid_audience_protocol_mapper


Patch:
diff --git a/example/main.tf b/example/main.tf
index 8142678..6afd887 100644
--- a/example/main.tf
+++ b/example/main.tf
@@ -5,11 +5,11 @@ provider "keycloak" {
 }
 
 resource "keycloak_realm" "test" {
-	realm                = "test"
-	enabled              = true
-	display_name         = "foo"
+	realm        = "test"
+	enabled      = true
+	display_name = "foo"
 
-	account_theme        = "base"
+	account_theme = "base"
 
 	access_code_lifespan = "30m"
 }
@@ -31,8 +31,8 @@ resource "keycloak_group" "bar" {
 }
 
 resource "keycloak_user" "user" {
-	realm_id   = "${keycloak_realm.test.id}"
-	username   = "test-user"
+	realm_id = "${keycloak_realm.test.id}"
+	username = "test-user"
 
 	email      = "test-user@fakedomain.com"
 	first_name = "Testy"
@@ -40,8 +40,8 @@ resource "keycloak_user" "user" {
 }
 
 resource "keycloak_user" "another_user" {
-	realm_id   = "${keycloak_realm.test.id}"
-	username   = "another-test-user"
+	realm_id = "${keycloak_realm.test.id}"
+	username = "another-test-user"
 
 	email      = "another-test-user@fakedomain.com"
 	first_name = "Testy"
@@ -49,8 +49,8 @@ resource "keycloak_user" "another_user" {
 }
 
 resource "keycloak_user" "user_with_password" {
-	realm_id   = "${keycloak_realm.test.id}"
-	username   = "user-with-password"
+	realm_id = "${keycloak_realm.test.id}"
+	username = "user-with-password"
 
 	email      = "user-with-password@fakedomain.com"
 	first_name = "Testy"
@@ -61,52 +61,51 @@ resource "keycloak_user" "user_with_password" {
 	}
 }
 
-
 resource "keycloak_group_memberships" "foo_members" {
 	realm_id = "${keycloak_realm.test.id}"
 	group_id = "${keycloak_group.foo.id}"
 
-	members  = [
+	members = [
 		"${keycloak_user.user.username}",
 		"${keycloak_user.another_user.username}"
 	]
 }
 
 resource "keycloak_openid_client" "test_client" {
-	client_id             = "test-openid-client"
-	name                  = "test-openid-client"
-	realm_id              = "${keycloak_realm.test.id}"
-	description           = "a test openid client"
+	client_id   = "test-openid-client"
+	name        = "test-openid-client"
+	realm_id    = "${keycloak_realm.test.id}"
+	description = "a test openid client"
 
 	standard_flow_enabled = true
 
-	access_type           = "CONFIDENTIAL"
-	valid_redirect_uris   = [
+	access_type         = "CONFIDENTIAL"
+	valid_redirect_uris = [
 		"http://localhost:5555/callback"
 	]
 
-	client_secret         = "secret"
+	client_secret = "secret"
 }
 
 resource "keycloak_openid_client_scope" "test_default_client_scope" {
-	name                = "test-default-client-scope"
-	realm_id            = "${keycloak_realm.test.id}"
+	name     = "test-default-client-scope"
+	realm_id = "${keycloak_realm.test.id}"
 
 	description         = "test"
 	consent_screen_text = "hello"
 }
 
 resource "keycloak_openid_client_scope" "test_optional_client_scope" {
-	name                = "test-optional-client-scope"
-	realm_id            = "${keycloak_realm.test.id}"
+	name     = "test-optional-client-scope"
+	realm_id = "${keycloak_realm.test.id}"
 
 	description         = "test"
 	consent_screen_text = "hello"
 }
 
 resource "keycloak_openid_client_default_scopes" "default_client_scopes" {
-	realm_id       = "${keycloak_realm.test.id}"
-	client_id      = "${keycloak_openid_client.test_client.id}"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "${keycloak_openid_client.test_client.id}"
 
 	default_scopes = [
 		"profile",
@@ -118,8 +117,8 @@ resource "keycloak_openid_client_default_scopes" "default_client_scopes" {
 }
 
 resource "keycloak_openid_client_optional_scopes" "optional_client_scopes" {
-	realm_id       = "${keycloak_realm.test.id}"
-	client_id      = "${keycloak_openid_client.test_client.id}"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "${keycloak_openid_client.test_client.id}"
 
 	optional_scopes = [
 		"address",
@@ -130,11 +129,11 @@ resource "keycloak_openid_client_optional_scopes" "optional_client_scopes" {
 }
 
 resource "keycloak_ldap_user_federation" "openldap" {
-	name                    = "openldap"
-	realm_id                = "${keycloak_realm.test.id}"
+	name     = "openldap"
+	realm_id = "${keycloak_realm.test.id}"
 
-	enabled                 = true
-	import_enabled          = false
+	enabled        = true
+	import_enabled = false
 
 	username_ldap_attribute = "cn"
 	rdn_ldap_attribute      = "cn"
@@ -148,27 +147,27 @@ resource "keycloak_ldap_user_federation" "openldap" {
 	bind_dn                 = "cn=admin,dc=example,dc=org"
 	bind_credential         = "admin"
 
-	connection_timeout      = "5s"
-	read_timeout            = "10s"
+	connection_timeout = "5s"
+	read_timeout       = "10s"
 
-	cache_policy            = "NO_CACHE"
+	cache_policy = "NO_CACHE"
 }
 
 resource "keycloak_ldap_user_attribute_mapper" "description_attr_mapper" {
-	name                        = "description-mapper"
-	realm_id                    = "${keycloak_ldap_user_federation.openldap.realm_id}"
-	ldap_user_federation_id     = "${keycloak_ldap_user_federation.openldap.id}"
+	name                    = "description-mapper"
+	realm_id                = "${keycloak_ldap_user_federation.openldap.realm_id}"
+	ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}"
 
-	user_model_attribute        = "description"
-	ldap_attribute              = "description"
+	user_model_attribute = "description"
+	ldap_attribute       = "description"
 
 	always_read_value_from_ldap = false
 }
 
 resource "keycloak_ldap_group_mapper" "group_mapper" {
-	name                           = "group mapper"
-	realm_id                       = "${keycloak_ldap_user_federation.openldap.realm_id}"
-	ldap_user_federation_id        = "${keycloak_ldap_user_federation.openldap.id}"
+	name                    = "group mapper"
+	realm_id                = "${keycloak_ldap_user_federation.openldap.realm_id}"
+	ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}"
 
 	ldap_groups_dn                 = "dc=example,dc=org"
 	group_name_ldap_attribute      = "cn"
@@ -188,9 +187,9 @@ resource "keycloak_ldap_msad_user_account_control_mapper" "msad_uac_mapper" {
 }
 
 resource "keycloak_ldap_full_name_mapper" "full_name_mapper" {
-	name                     = "full-name-mapper"
-	realm_id                 = "${keycloak_ldap_user_federation.openldap.realm_id}"
-	ldap_user_federation_id  = "${keycloak_ldap_user_federation.openldap.id}"
+	name                    = "full-name-mapper"
+	realm_id                = "${keycloak_ldap_user_federation.openldap.realm_id}"
+	ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}"
 
 	ldap_full_name_attribute = "cn"
 	read_only                = true
@@ -201,7 +200,7 @@ resource "keycloak_custom_user_federation" "custom" {
 	realm_id    = "master"
 	provider_id = "custom"
 
-	enabled     = true
+	enabled = true
 }
 
 resource "keycloak_openid_user_attribute_protocol_mapper" "map_user_attributes_client" {
@@ -263,9 +262,9 @@ resource "keycloak_openid_user_property_protocol_mapper" "map_user_properties_cl
 }
 
 resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_client" {
-	name        = "tf-test-open-id-hardcoded-claim-protocol-mapper-client"
-	realm_id    = "${keycloak_realm.test.id}"
-	client_id   = "${keycloak_openid_client.test_client.id}"
+	name      = "tf-test-open-id-hardcoded-claim-protocol-mapper-client"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "${keycloak_openid_client.test_client.id}"
 
 	claim_name  = "foo"
 	claim_value = "bar"
@@ -276,27 +275,58 @@ resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_clie
 	realm_id        = "${keycloak_realm.test.id}"
 	client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}"
 
-	claim_name      = "foo"
-	claim_value     = "bar"
+	claim_name  = "foo"
+	claim_value = "bar"
+}
+
+resource "keycloak_openid_client" "bearer_only_client" {
+	client_id   = "test-bearer-only-client"
+	name        = "test-bearer-only-client"
+	realm_id    = "${keycloak_realm.test.id}"
+	description = "a test openid client using bearer-only"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_client_scope" {
+	name            = "tf-test-openid-audience-protocol-mapper-client-scope"
+	realm_id        = "${keycloak_realm.test.id}"
+	client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}"
+
+	add_to_id_token     = true
+	add_to_access_token = false
+
+	included_client_audience = "${keycloak_openid_client.bearer_only_client.client_id}"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_client" {
+	name      = "tf-test-openid-audience-protocol-mapper-client"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "${keycloak_openid_client.test_client.id}"
+
+	add_to_id_token     = false
+	add_to_access_token = true
+
+	included_custom_audience = "foo"
 }
 
 resource "keycloak_saml_client" "saml_client" {
-	realm_id                = "${keycloak_realm.test.id}"
-	client_id               = "test-saml-client"
-	name                    = "test-saml-client"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "test-saml-client"
+	name      = "test-saml-client"
 
 	sign_documents          = false
 	sign_assertions         = true
 	include_authn_statement = true
 
-	signing_certificate     = "${file("../provider/misc/saml-cert.pem")}"
-	signing_private_key     = "${file("../provider/misc/saml-key.pem")}"
+	signing_certificate = "${file("../provider/misc/saml-cert.pem")}"
+	signing_private_key = "${file("../provider/misc/saml-key.pem")}"
 }
 
 resource "keycloak_saml_user_attribute_protocol_mapper" "saml_user_attribute_mapper" {
-	realm_id                   = "${keycloak_realm.test.id}"
-	client_id                  = "${keycloak_saml_client.saml_client.id}"
-	name                       = "test-saml-user-attribute-mapper"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "${keycloak_saml_client.saml_client.id}"
+	name      = "test-saml-user-attribute-mapper"
 
 	user_attribute             = "user-attribute"
 	friendly_name              = "friendly-name"
@@ -305,9 +335,9 @@ resource "keycloak_saml_user_attribute_protocol_mapper" "saml_user_attribute_map
 }
 
 resource "keycloak_saml_user_property_protocol_mapper" "saml_user_property_mapper" {
-	realm_id                   = "${keycloak_realm.test.id}"
-	client_id                  = "${keycloak_saml_client.saml_client.id}"
-	name                       = "test-saml-user-property-mapper"
+	realm_id  = "${keycloak_realm.test.id}"
+	client_id = "${keycloak_saml_client.saml_client.id}"
+	name      = "test-saml-user-property-mapper"
 
 	user_property              = "email"
 	saml_attribute_name        = "email"
diff --git a/keycloak/generic_client.go b/keycloak/generic_client.go
new file mode 100644
index 0000000..5f97220
--- /dev/null
+++ b/keycloak/generic_client.go
@@ -0,0 +1,29 @@
+package keycloak
+
+import "fmt"
+
+type GenericClient struct {
+	Id       string `json:"id,omitempty"`
+	ClientId string `json:"clientId"`
+	RealmId  string `json:"-"`
+	Name     string `json:"name"`
+	Protocol string `json:"protocol"`
+
+	Enabled     bool   `json:"enabled"`
+	Description string `json:"description"`
+}
+
+func (keycloakClient *KeycloakClient) listGenericClients(realmId string) ([]*GenericClient, error) {
+	var clients []*GenericClient
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients", realmId), &clients)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, client := range clients {
+		client.RealmId = realmId
+	}
+
+	return clients, nil
+}
diff --git a/keycloak/openid_audience_protocol_mapper.go b/keycloak/openid_audience_protocol_mapper.go
new file mode 100644
index 0000000..93eef65
--- /dev/null
+++ b/keycloak/openid_audience_protocol_mapper.go
@@ -0,0 +1,133 @@
+package keycloak
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type OpenIdAudienceProtocolMapper struct {
+	Id            string
+	Name          string
+	RealmId       string
+	ClientId      string
+	ClientScopeId string
+
+	AddToIdToken     bool
+	AddToAccessToken bool
+
+	IncludedClientAudience string
+	IncludedCustomAudience string
+}
+
+func (mapper *OpenIdAudienceProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
+	return &protocolMapper{
+		Id:             mapper.Id,
+		Name:           mapper.Name,
+		Protocol:       "openid-connect",
+		ProtocolMapper: "oidc-audience-mapper",
+		Config: map[string]string{
+			addToIdTokenField:           strconv.FormatBool(mapper.AddToIdToken),
+			addToAccessTokenField:       strconv.FormatBool(mapper.AddToAccessToken),
+			includedClientAudienceField: mapper.IncludedClientAudience,
+			includedCustomAudienceField: mapper.IncludedCustomAudience,
+		},
+	}
+}
+
+func (protocolMapper *protocolMapper) convertToOpenIdAudienceProtocolMapper(realmId, clientId, clientScopeId string) (*OpenIdAudienceProtocolMapper, error) {
+	addToIdToken, err := strconv.ParseBool(protocolMapper.Config[addToIdTokenField])
+	if err != nil {
+		return nil, err
+	}
+
+	addToAccessToken, err := strconv.ParseBool(protocolMapper.Config[addToAccessTokenField])
+	if err != nil {
+		return nil, err
+	}
+
+	return &OpenIdAudienceProtocolMapper{
+		Id:            protocolMapper.Id,
+		Name:          protocolMapper.Name,
+		RealmId:       realmId,
+		ClientId:      clientId,
+		ClientScopeId: clientScopeId,
+
+		AddToIdToken:     addToIdToken,
+		AddToAccessToken: addToAccessToken,
+
+		IncludedClientAudience: protocolMapper.Config[includedClientAudienceField],
+		IncludedCustomAudience: protocolMapper.Config[includedCustomAudienceField],
+	}, nil
+}
+
+func (keycloakClient *KeycloakClient) GetOpenIdAudienceProtocolMapper(realmId, clientId, clientScopeId, mapperId string) (*OpenIdAudienceProtocolMapper, error) {
+	var protocolMapper *protocolMapper
+
+	err := keycloakClient.get(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &protocolMapper)
+	if err != nil {
+		return nil, err
+	}
+
+	return protocolMapper.convertToOpenIdAudienceProtocolMapper(realmId, clientId, clientScopeId)
+}
+
+func (keycloakClient *KeycloakClient) DeleteOpenIdAudienceProtocolMapper(realmId, clientId, clientScopeId, mapperId string) error {
+	return keycloakClient.delete(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId))
+}
+
+func (keycloakClient *KeycloakClient) NewOpenIdAudienceProtocolMapper(mapper *OpenIdAudienceProtocolMapper) error {
+	path := protocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)
+
+	location, err := keycloakClient.post(path, mapper.convertToGenericProtocolMapper())
+	if err != nil {
+		return err
+	}
+
+	mapper.Id = getIdFromLocationHeader(location)
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) UpdateOpenIdAudienceProtocolMapper(mapper *OpenIdAudienceProtocolMapper) error {
+	path := individualProtocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)
+
+	return keycloakClient.put(path, mapper.convertToGenericProtocolMapper())
+}
+
+func (keycloakClient *KeycloakClient) ValidateOpenIdAudienceProtocolMapper(mapper *OpenIdAudienceProtocolMapper) error {
+	if mapper.ClientId == "" && mapper.ClientScopeId == "" {
+		return fmt.Errorf("validation error: one of ClientId or ClientScopeId must be set")
+	}
+
+	if mapper.IncludedClientAudience == "" && mapper.IncludedCustomAudience == "" {
+		return fmt.Errorf("validation error: one of IncludedClientAudience or IncludedCustomAudience must be set")
+	}
+
+	protocolMappers, err := keycloakClient.listGenericProtocolMappers(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)
+	if err != nil {
+		return err
+	}
+
+	for _, protocolMapper := range protocolMappers {
+		if protocolMapper.Name == mapper.Name && protocolMapper.Id != mapper.Id {
+			return fmt.Errorf("validation error: a protocol mapper with name %s already exists for this client", mapper.Name)
+		}
+	}
+
+	if mapper.IncludedClientAudience != "" {
+		clients, err := keycloakClient.listGenericClients(mapper.RealmId)
+		if err != nil {
+			return err
+		}
+
+		for _, client := range clients {
+			if client.ClientId == mapper.IncludedClientAudience {
+				return nil
+			}
+		}
+
+		return fmt.Errorf("validation error: client %s does not exist", mapper.IncludedClientAudience)
+	}
+
+	return nil
+}
diff --git a/keycloak/protocol_mapper.go b/keycloak/protocol_mapper.go
index 561687c..5146547 100644
--- a/keycloak/protocol_mapper.go
+++ b/keycloak/protocol_mapper.go
@@ -12,19 +12,21 @@ type protocolMapper struct {
 }
 
 var (
-	addToAccessTokenField    = "access.token.claim"
-	addToIdTokenField        = "id.token.claim"
-	addToUserInfoField       = "userinfo.token.claim"
-	attributeNameField       = "attribute.name"
-	attributeNameFormatField = "attribute.nameformat"
-	claimNameField           = "claim.name"
-	claimValueField          = "claim.value"
-	claimValueTypeField      = "jsonType.label"
-	friendlyNameField        = "friendly.name"
-	fullPathField            = "full.path"
-	multivaluedField         = "multivalued"
-	userAttributeField       = "user.attribute"
-	userPropertyField        = "user.attribute"
+	addToAccessTokenField       = "access.token.claim"
+	addToIdTokenField           = "id.token.claim"
+	addToUserInfoField          = "userinfo.token.claim"
+	attributeNameField          = "attribute.name"
+	attributeNameFormatField    = "attribute.nameformat"
+	claimNameField              = "claim.name"
+	claimValueField             = "claim.value"
+	claimValueTypeField         = "jsonType.label"
+	friendlyNameField           = "friendly.name"
+	fullPathField               = "full.path"
+	includedClientAudienceField = "included.client.audience"
+	includedCustomAudienceField = "included.custom.audience"
+	multivaluedField            = "multivalued"
+	userAttributeField          = "user.attribute"
+	userPropertyField           = "user.attribute"
 )
 
 func protocolMapperPath(realmId, clientId, clientScopeId string) string {
diff --git a/provider/keycloak_openid_audience_protocol_mapper.go b/provider/keycloak_openid_audience_protocol_mapper.go
new file mode 100644
index 0000000..8614ee5
--- /dev/null
+++ b/provider/keycloak_openid_audience_protocol_mapper.go
@@ -0,0 +1,175 @@
+package provider
+
+import (
+	"github.com/hashicorp/terraform/helper/schema"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+)
+
+func resourceKeycloakOpenIdAudienceProtocolMapper() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakOpenIdAudienceProtocolMapperCreate,
+		Read:   resourceKeycloakOpenIdAudienceProtocolMapperRead,
+		Update: resourceKeycloakOpenIdAudienceProtocolMapperUpdate,
+		Delete: resourceKeycloakOpenIdAudienceProtocolMapperDelete,
+		Importer: &schema.ResourceImporter{
+			// import a mapper tied to a client:
+			// {{realmId}}/client/{{clientId}}/{{protocolMapperId}}
+			// or a client scope:
+			// {{realmId}}/client-scope/{{clientScopeId}}/{{protocolMapperId}}
+			State: genericProtocolMapperImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"name": {
+				Type:        schema.TypeString,
+				Required:    true,
+				Description: "A human-friendly name that will appear in the Keycloak console.",
+			},
+			"realm_id": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: "The realm id where the associated client or client scope exists.",
+			},
+			"client_id": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ForceNew:      true,
+				Description:   "The mapper's associated client. Cannot be used at the same time as client_scope_id.",
+				ConflictsWith: []string{"client_scope_id"},
+			},
+			"client_scope_id": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ForceNew:      true,
+				Description:   "The mapper's associated client scope. Cannot be used at the same time as client_id.",
+				ConflictsWith: []string{"client_id"},
+			},
+			"included_client_audience": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				Description:   "A client ID to include within the token's `aud` claim. Cannot be used with included_custom_audience",
+				ConflictsWith: []string{"included_custom_audience"},
+			},
+			"included_custom_audience": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				Description:   "A custom audience to include within the token's `aud` claim.  Cannot be used with included_custom_audience",
+				ConflictsWith: []string{"included_client_audience"},
+			},
+			"add_to_id_token": {
+				Type:        schema.TypeBool,
+				Optional:    true,
+				Default:     true,
+				Description: "Indicates if this claim should be added to the id token.",
+			},
+			"add_to_access_token": {
+				Type:        schema.TypeBool,
+				Optional:    true,
+				Default:     true,
+				Description: "Indicates if this claim should be added to the access token.",
+			},
+		},
+	}
+}
+
+func mapFromDataToOpenIdAudienceProtocolMapper(data *schema.ResourceData) *keycloak.OpenIdAudienceProtocolMapper {
+	return &keycloak.OpenIdAudienceProtocolMapper{
+		Id:            data.Id(),
+		Name:          data.Get("name").(string),
+		RealmId:       data.Get("realm_id").(string),
+		ClientId:      data.Get("client_id").(string),
+		ClientScopeId: data.Get("client_scope_id").(string),
+
+		AddToIdToken:     data.Get("add_to_id_token").(bool),
+		AddToAccessToken: data.Get("add_to_access_token").(bool),
+
+		IncludedClientAudience: data.Get("included_client_audience").(string),
+		IncludedCustomAudience: data.Get("included_custom_audience").(string),
+	}
+}
+
+func mapFromOpenIdAudienceMapperToData(mapper *keycloak.OpenIdAudienceProtocolMapper, data *schema.ResourceData) {
+	data.SetId(mapper.Id)
+	data.Set("name", mapper.Name)
+	data.Set("realm_id", mapper.RealmId)
+
+	if mapper.ClientId != "" {
+		data.Set("client_id", mapper.ClientId)
+	} else {
+		data.Set("client_scope_id", mapper.ClientScopeId)
+	}
+
+	if mapper.IncludedClientAudience != "" {
+		data.Set("included_client_audience", mapper.IncludedClientAudience)
+	} else {
+		data.Set("included_custom_audience", mapper.IncludedCustomAudience)
+	}
+
+	data.Set("add_to_id_token", mapper.AddToIdToken)
+	data.Set("add_to_access_token", mapper.AddToAccessToken)
+}
+
+func resourceKeycloakOpenIdAudienceProtocolMapperCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	openIdAudienceMapper := mapFromDataToOpenIdAudienceProtocolMapper(data)
+
+	err := keycloakClient.ValidateOpenIdAudienceProtocolMapper(openIdAudienceMapper)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.NewOpenIdAudienceProtocolMapper(openIdAudienceMapper)
+	if err != nil {
+		return err
+	}
+
+	mapFromOpenIdAudienceMapperToData(openIdAudienceMapper, data)
+
+	return resourceKeycloakOpenIdAudienceProtocolMapperRead(data, meta)
+}
+
+func resourceKeycloakOpenIdAudienceProtocolMapperRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	clientScopeId := data.Get("client_scope_id").(string)
+
+	openIdAudienceMapper, err := keycloakClient.GetOpenIdAudienceProtocolMapper(realmId, clientId, clientScopeId, data.Id())
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	mapFromOpenIdAudienceMapperToData(openIdAudienceMapper, data)
+
+	return nil
+}
+
+func resourceKeycloakOpenIdAudienceProtocolMapperUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	openIdAudienceMapper := mapFromDataToOpenIdAudienceProtocolMapper(data)
+
+	err := keycloakClient.ValidateOpenIdAudienceProtocolMapper(openIdAudienceMapper)
+	if err != nil {
+		return err
+	}
+
+	err = keycloakClient.UpdateOpenIdAudienceProtocolMapper(openIdAudienceMapper)
+	if err != nil {
+		return err
+	}
+
+	return resourceKeycloakOpenIdAudienceProtocolMapperRead(data, meta)
+}
+
+func resourceKeycloakOpenIdAudienceProtocolMapperDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	clientId := data.Get("client_id").(string)
+	clientScopeId := data.Get("client_scope_id").(string)
+
+	return keycloakClient.DeleteOpenIdAudienceProtocolMapper(realmId, clientId, clientScopeId, data.Id())
+}
diff --git a/provider/provider.go b/provider/provider.go
index 2c99f18..823d842 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -25,6 +25,7 @@ func KeycloakProvider() *schema.Provider {
 			"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_client_default_scopes":            resourceKeycloakOpenidClientDefaultScopes(),
 			"keycloak_openid_client_optional_scopes":           resourceKeycloakOpenidClientOptionalScopes(),
 			"keycloak_saml_client":                             resourceKeycloakSamlClient(),


Test patch:
diff --git a/provider/keycloak_openid_audience_protocol_mapper_test.go b/provider/keycloak_openid_audience_protocol_mapper_test.go
new file mode 100644
index 0000000..306dcb6
--- /dev/null
+++ b/provider/keycloak_openid_audience_protocol_mapper_test.go
@@ -0,0 +1,518 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform/helper/acctest"
+	"github.com/hashicorp/terraform/helper/resource"
+	"github.com/hashicorp/terraform/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"regexp"
+	"testing"
+)
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_basicClient(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper_client"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_basic_client(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_basicClientScope(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_basic_clientScope(realmName, clientScopeId, mapperName),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_import(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-openid-client-" + acctest.RandString(10)
+	clientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	clientResourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper_client"
+	clientScopeResourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_import(realmName, clientId, clientScopeId, mapperName),
+				Check: resource.ComposeTestCheckFunc(
+					testKeycloakOpenIdAudienceProtocolMapperExists(clientResourceName),
+					testKeycloakOpenIdAudienceProtocolMapperExists(clientScopeResourceName),
+				),
+			},
+			{
+				ResourceName:      clientResourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getGenericProtocolMapperIdForClient(clientResourceName),
+			},
+			{
+				ResourceName:      clientScopeResourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getGenericProtocolMapperIdForClientScope(clientScopeResourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_update(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	customAudience := "terraform-audience-" + acctest.RandString(10)
+	updatedCustomAudience := "terraform-audience-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_customAudience(realmName, clientId, mapperName, customAudience),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_customAudience(realmName, clientId, mapperName, updatedCustomAudience),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_createAfterManualDestroy(t *testing.T) {
+	var mapper = &keycloak.OpenIdAudienceProtocolMapper{}
+
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper_client"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_basic_client(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperFetch(resourceName, mapper),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteOpenIdAudienceProtocolMapper(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)
+					if err != nil {
+						t.Error(err)
+					}
+				},
+				Config: testKeycloakOpenIdAudienceProtocolMapper_basic_client(realmName, clientId, mapperName),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_updateClientIdForceNew(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	updatedClientId := "terraform-client-update-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	customAudience := "terraform-audience-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_customAudience(realmName, clientId, mapperName, customAudience),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_customAudience(realmName, updatedClientId, mapperName, customAudience),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_updateClientScopeForceNew(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+	clientScopeId := "terraform-client-" + acctest.RandString(10)
+	newClientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper_client_scope"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_basic_clientScope(realmName, clientScopeId, mapperName),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_basic_clientScope(realmName, newClientScopeId, mapperName),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_updateRealmIdForceNew(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	newRealmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	customAudience := "terraform-audience-" + acctest.RandString(10)
+	resourceName := "keycloak_openid_audience_protocol_mapper.audience_mapper"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_customAudience(realmName, clientId, mapperName, customAudience),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+			{
+				Config: testKeycloakOpenIdAudienceProtocolMapper_customAudience(newRealmName, clientId, mapperName, customAudience),
+				Check:  testKeycloakOpenIdAudienceProtocolMapperExists(resourceName),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_validateClientConflictsWithClientScope(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	clientScopeId := "terraform-client-scope-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakOpenIdAudienceProtocolMapper_validateClientConflictsWithClientScope(realmName, clientId, clientScopeId, mapperName),
+				ExpectError: regexp.MustCompile(".+ conflicts with .+"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_validateClientAudienceConflictsWithCustomAudience(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakOpenIdAudienceProtocolMapper_validateClientAudienceConflictsWithCustomAudience(realmName, clientId, mapperName),
+				ExpectError: regexp.MustCompile(".+ conflicts with .+"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakOpenIdAudienceProtocolMapper_validateClientAudienceExists(t *testing.T) {
+	realmName := "terraform-realm-" + acctest.RandString(10)
+	clientId := "terraform-client-" + acctest.RandString(10)
+	mapperName := "terraform-openid-connect-audience-mapper-" + acctest.RandString(5)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccKeycloakOpenIdAudienceProtocolMapperDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config:      testKeycloakOpenIdAudienceProtocolMapper_validateClientAudienceExists(realmName, clientId, mapperName),
+				ExpectError: regexp.MustCompile("validation error: client .+ does not exist"),
+			},
+		},
+	})
+}
+
+func testAccKeycloakOpenIdAudienceProtocolMapperDestroy() resource.TestCheckFunc {
+	return func(state *terraform.State) error {
+		for resourceName, rs := range state.RootModule().Resources {
+			if rs.Type != "keycloak_openid_audience_protocol_mapper" {
+				continue
+			}
+
+			mapper, _ := getAudienceMapperUsingState(state, resourceName)
+
+			if mapper != nil {
+				return fmt.Errorf("openid audience protocol mapper with id %s still exists", rs.Primary.ID)
+			}
+		}
+
+		return nil
+	}
+}
+
+func testKeycloakOpenIdAudienceProtocolMapperExists(resourceName string) resource.TestCheckFunc {
+	return func(state *terraform.State) error {
+		_, err := getAudienceMapperUsingState(state, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testKeycloakOpenIdAudienceProtocolMapperFetch(resourceName string, mapper *keycloak.OpenIdAudienceProtocolMapper) resource.TestCheckFunc {
+	return func(state *terraform.State) error {
+		fetchedMapper, err := getAudienceMapperUsingState(state, resourceName)
+		if err != nil {
+			return err
+		}
+
+		mapper.Id = fetchedMapper.Id
+		mapper.ClientId = fetchedMapper.ClientId
+		mapper.ClientScopeId = fetchedMapper.ClientScopeId
+		mapper.RealmId = fetchedMapper.RealmId
+
+		return nil
+	}
+}
+
+func getAudienceMapperUsingState(state *terraform.State, resourceName string) (*keycloak.OpenIdAudienceProtocolMapper, error) {
+	rs, ok := state.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found in TF state: %s ", resourceName)
+	}
+
+	id := rs.Primary.ID
+	realm := rs.Primary.Attributes["realm_id"]
+	clientId := rs.Primary.Attributes["client_id"]
+	clientScopeId := rs.Primary.Attributes["client_scope_id"]
+
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	return keycloakClient.GetOpenIdAudienceProtocolMapper(realm, clientId, clientScopeId, id)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_basic_client(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper_client" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_id                = "${keycloak_openid_client.openid_client.id}"
+
+	included_custom_audience = "foo"
+}`, realmName, clientId, mapperName)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_basic_clientScope(realmName, clientScopeId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper_client_scope" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_scope_id          = "${keycloak_openid_client_scope.client_scope.id}"
+
+	included_custom_audience = "foo"
+}`, realmName, clientScopeId, mapperName)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_import(realmName, clientId, clientScopeId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id    = "${keycloak_realm.realm.id}"
+	client_id   = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper_client" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_id                = "${keycloak_openid_client.openid_client.id}"
+
+	included_custom_audience = "foo"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper_client_scope" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_scope_id          = "${keycloak_openid_client_scope.client_scope.id}"
+
+	included_custom_audience = "foo"
+}`, realmName, clientId, mapperName, clientScopeId, mapperName)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_customAudience(realmName, clientId, mapperName, customAudience string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "%s"
+
+	access_type = "CONFIDENTIAL"
+
+	standard_flow_enabled = true
+
+	valid_redirect_uris = ["http://localhost:5555/callback"]
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_id                = "${keycloak_openid_client.openid_client.id}"
+
+	included_custom_audience = "%s"
+}`, realmName, clientId, mapperName, customAudience)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_validateClientConflictsWithClientScope(realmName, clientId, clientScopeId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_client_scope" "client_scope" {
+	name     = "%s"
+	realm_id = "${keycloak_realm.realm.id}"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_id                = "${keycloak_openid_client.openid_client.id}"
+	client_scope_id          = "${keycloak_openid_client_scope.client_scope.id}"
+
+	included_custom_audience = "foo"
+}`, realmName, clientId, clientScopeId, mapperName)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_validateClientAudienceConflictsWithCustomAudience(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "%s"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_id                = "${keycloak_openid_client.openid_client.id}"
+
+	included_client_audience = "${keycloak_openid_client.openid_client.client_id}"
+	included_custom_audience = "foo"
+}`, realmName, clientId, mapperName)
+}
+
+func testKeycloakOpenIdAudienceProtocolMapper_validateClientAudienceExists(realmName, clientId, mapperName string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "openid_client" {
+	realm_id  = "${keycloak_realm.realm.id}"
+	client_id = "openid-client"
+
+	access_type = "BEARER-ONLY"
+}
+
+resource "keycloak_openid_audience_protocol_mapper" "audience_mapper" {
+	name                     = "%s"
+	realm_id                 = "${keycloak_realm.realm.id}"
+	client_id                = "${keycloak_openid_client.openid_client.id}"
+
+	included_client_audience = "%s"
+
+	depends_on = [ "keycloak_openid_client.openid_client" ]
+}`, realmName, mapperName, clientId)
+}