From 66319a4ec317f7ba2e32109dedca92cdc3d5d902 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Tue, 4 Oct 2022 14:38:28 -0500 Subject: [PATCH] chore: rename keycloak_generic_client_protocol_mapper to keycloak_generic_protocol_mapper (#742) --- .../generic_client_description_converter.go | 72 ++--- keycloak/generic_client_protocol_mapper.go | 52 ++-- provider/provider.go | 1 + ...keycloak_generic_client_protocol_mapper.go | 15 +- ...oak_generic_client_protocol_mapper_test.go | 4 +- ...source_keycloak_generic_protocol_mapper.go | 168 ++++++++++++ ...e_keycloak_generic_protocol_mapper_test.go | 259 ++++++++++++++++++ 7 files changed, 500 insertions(+), 71 deletions(-) create mode 100644 provider/resource_keycloak_generic_protocol_mapper.go create mode 100644 provider/resource_keycloak_generic_protocol_mapper_test.go diff --git a/keycloak/generic_client_description_converter.go b/keycloak/generic_client_description_converter.go index 122be20dd..f08bdac62 100644 --- a/keycloak/generic_client_description_converter.go +++ b/keycloak/generic_client_description_converter.go @@ -8,42 +8,42 @@ import ( // https://www.keycloak.org/docs-api/6.0/javadocs/org/keycloak/representations/idm/ClientRepresentation.html type GenericClientRepresentation struct { - Access map[string]string `json:"access"` - AdminUrl string `json:"adminUrl"` - Attributes map[string]string `json:"attributes"` - AuthenticationFlowBindingOverrides map[string]string `json:"authenticationFlowBindingOverrides"` - AuthorizationServicesEnabled bool `json:"authorizationServicesEnabled"` - AuthorizationSettings map[string]string `json:"authorizationSettings"` - BaseUrl string `json:"baseUrl"` - BearerOnly bool `json:"bearerOnly"` - ClientAuthenticatorType string `json:"clientAuthenticatorType"` - ClientId string `json:"clientId"` - ConsentRequired string `json:"consentRequired"` - DefaultClientScopes []string `json:"defaultClientScopes"` - DefaultRoles []string `json:"defaultRoles"` - Description string `json:"description"` - DirectAccessGrantsEnabled bool `json:"directAccessGrantsEnabled"` - Enabled bool `json:"enabled"` - FrontchannelLogout bool `json:"frontchannelLogout"` - FullScopeAllowed bool `json:"fullScopeAllowed"` - Id string `json:"id"` - ImplicitFlowEnabled bool `json:"implicitFlowEnabled"` - Name string `json:"name"` - NotBefore int `json:"notBefore"` - OptionalClientScopes []string `json:"optionalClientScopes"` - Origin string `json:"origin"` - Protocol string `json:"protocol"` - ProtocolMappers []*GenericClientProtocolMapper `json:"protocolMappers"` - PublicClient bool `json:"publicClient"` - RedirectUris []string `json:"redirectUris"` - RegisteredNodes map[string]string `json:"registeredNodes"` - RegistrationAccessToken string `json:"registrationAccessToken"` - RootUrl string `json:"rootUrl"` - Secret string `json:"secret"` - ServiceAccountsEnabled bool `json:"serviceAccountsEnabled"` - StandardFlowEnabled bool `json:"standardFlowEnabled"` - SurrogateAuthRequired bool `json:"surrogateAuthRequired"` - WebOrigins []string `json:"webOrigins"` + Access map[string]string `json:"access"` + AdminUrl string `json:"adminUrl"` + Attributes map[string]string `json:"attributes"` + AuthenticationFlowBindingOverrides map[string]string `json:"authenticationFlowBindingOverrides"` + AuthorizationServicesEnabled bool `json:"authorizationServicesEnabled"` + AuthorizationSettings map[string]string `json:"authorizationSettings"` + BaseUrl string `json:"baseUrl"` + BearerOnly bool `json:"bearerOnly"` + ClientAuthenticatorType string `json:"clientAuthenticatorType"` + ClientId string `json:"clientId"` + ConsentRequired string `json:"consentRequired"` + DefaultClientScopes []string `json:"defaultClientScopes"` + DefaultRoles []string `json:"defaultRoles"` + Description string `json:"description"` + DirectAccessGrantsEnabled bool `json:"directAccessGrantsEnabled"` + Enabled bool `json:"enabled"` + FrontchannelLogout bool `json:"frontchannelLogout"` + FullScopeAllowed bool `json:"fullScopeAllowed"` + Id string `json:"id"` + ImplicitFlowEnabled bool `json:"implicitFlowEnabled"` + Name string `json:"name"` + NotBefore int `json:"notBefore"` + OptionalClientScopes []string `json:"optionalClientScopes"` + Origin string `json:"origin"` + Protocol string `json:"protocol"` + ProtocolMappers []*GenericProtocolMapper `json:"protocolMappers"` + PublicClient bool `json:"publicClient"` + RedirectUris []string `json:"redirectUris"` + RegisteredNodes map[string]string `json:"registeredNodes"` + RegistrationAccessToken string `json:"registrationAccessToken"` + RootUrl string `json:"rootUrl"` + Secret string `json:"secret"` + ServiceAccountsEnabled bool `json:"serviceAccountsEnabled"` + StandardFlowEnabled bool `json:"standardFlowEnabled"` + SurrogateAuthRequired bool `json:"surrogateAuthRequired"` + WebOrigins []string `json:"webOrigins"` } func (keycloakClient *KeycloakClient) NewGenericClientDescription(ctx context.Context, realmId string, body string) (*GenericClientRepresentation, error) { diff --git a/keycloak/generic_client_protocol_mapper.go b/keycloak/generic_client_protocol_mapper.go index 2eb2e56db..63a48668a 100644 --- a/keycloak/generic_client_protocol_mapper.go +++ b/keycloak/generic_client_protocol_mapper.go @@ -5,7 +5,7 @@ import ( "fmt" ) -type GenericClientProtocolMapper struct { +type GenericProtocolMapper struct { ClientId string `json:"-"` ClientScopeId string `json:"-"` Config map[string]string `json:"config"` @@ -16,71 +16,71 @@ type GenericClientProtocolMapper struct { RealmId string `json:"-"` } -type OpenidClientWithGenericClientProtocolMappers struct { +type OpenidClientWithGenericProtocolMappers struct { OpenidClient - ProtocolMappers []*GenericClientProtocolMapper + ProtocolMappers []*GenericProtocolMapper } -func (keycloakClient *KeycloakClient) NewGenericClientProtocolMapper(ctx context.Context, genericClientProtocolMapper *GenericClientProtocolMapper) error { - path := protocolMapperPath(genericClientProtocolMapper.RealmId, genericClientProtocolMapper.ClientId, genericClientProtocolMapper.ClientScopeId) +func (keycloakClient *KeycloakClient) NewGenericProtocolMapper(ctx context.Context, genericProtocolMapper *GenericProtocolMapper) error { + path := protocolMapperPath(genericProtocolMapper.RealmId, genericProtocolMapper.ClientId, genericProtocolMapper.ClientScopeId) - _, location, err := keycloakClient.post(ctx, path, genericClientProtocolMapper) + _, location, err := keycloakClient.post(ctx, path, genericProtocolMapper) if err != nil { return err } - genericClientProtocolMapper.Id = getIdFromLocationHeader(location) + genericProtocolMapper.Id = getIdFromLocationHeader(location) return nil } -func (keycloakClient *KeycloakClient) GetGenericClientProtocolMappers(ctx context.Context, realmId string, clientId string) (*OpenidClientWithGenericClientProtocolMappers, error) { - var openidClientWithGenericClientProtocolMappers OpenidClientWithGenericClientProtocolMappers +func (keycloakClient *KeycloakClient) GetGenericProtocolMappers(ctx context.Context, realmId string, clientId string) (*OpenidClientWithGenericProtocolMappers, error) { + var openidClientWithGenericProtocolMappers OpenidClientWithGenericProtocolMappers - err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/clients/%s", realmId, clientId), &openidClientWithGenericClientProtocolMappers, nil) + err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/clients/%s", realmId, clientId), &openidClientWithGenericProtocolMappers, nil) if err != nil { return nil, err } - openidClientWithGenericClientProtocolMappers.RealmId = realmId - openidClientWithGenericClientProtocolMappers.ClientId = clientId + openidClientWithGenericProtocolMappers.RealmId = realmId + openidClientWithGenericProtocolMappers.ClientId = clientId - for _, protocolMapper := range openidClientWithGenericClientProtocolMappers.ProtocolMappers { + for _, protocolMapper := range openidClientWithGenericProtocolMappers.ProtocolMappers { protocolMapper.RealmId = realmId protocolMapper.ClientId = clientId } - return &openidClientWithGenericClientProtocolMappers, nil + return &openidClientWithGenericProtocolMappers, nil } -func (keycloakClient *KeycloakClient) GetGenericClientProtocolMapper(ctx context.Context, realmId string, clientId string, clientScopeId string, mapperId string) (*GenericClientProtocolMapper, error) { - var genericClientProtocolMapper GenericClientProtocolMapper +func (keycloakClient *KeycloakClient) GetGenericProtocolMapper(ctx context.Context, realmId string, clientId string, clientScopeId string, mapperId string) (*GenericProtocolMapper, error) { + var genericProtocolMapper GenericProtocolMapper - err := keycloakClient.get(ctx, individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &genericClientProtocolMapper, nil) + err := keycloakClient.get(ctx, individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &genericProtocolMapper, nil) if err != nil { return nil, err } // these values are not provided by the keycloak API - genericClientProtocolMapper.ClientId = clientId - genericClientProtocolMapper.ClientScopeId = clientScopeId - genericClientProtocolMapper.RealmId = realmId + genericProtocolMapper.ClientId = clientId + genericProtocolMapper.ClientScopeId = clientScopeId + genericProtocolMapper.RealmId = realmId - return &genericClientProtocolMapper, nil + return &genericProtocolMapper, nil } -func (keycloakClient *KeycloakClient) UpdateGenericClientProtocolMapper(ctx context.Context, genericClientProtocolMapper *GenericClientProtocolMapper) error { - path := individualProtocolMapperPath(genericClientProtocolMapper.RealmId, genericClientProtocolMapper.ClientId, genericClientProtocolMapper.ClientScopeId, genericClientProtocolMapper.Id) +func (keycloakClient *KeycloakClient) UpdateGenericProtocolMapper(ctx context.Context, genericProtocolMapper *GenericProtocolMapper) error { + path := individualProtocolMapperPath(genericProtocolMapper.RealmId, genericProtocolMapper.ClientId, genericProtocolMapper.ClientScopeId, genericProtocolMapper.Id) - return keycloakClient.put(ctx, path, genericClientProtocolMapper) + return keycloakClient.put(ctx, path, genericProtocolMapper) } -func (keycloakClient *KeycloakClient) DeleteGenericClientProtocolMapper(ctx context.Context, realmId string, clientId string, clientScopeId string, mapperId string) error { +func (keycloakClient *KeycloakClient) DeleteGenericProtocolMapper(ctx context.Context, realmId string, clientId string, clientScopeId string, mapperId string) error { return keycloakClient.delete(ctx, individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), nil) } -func (mapper *GenericClientProtocolMapper) Validate(ctx context.Context, keycloakClient *KeycloakClient) error { +func (mapper *GenericProtocolMapper) Validate(ctx context.Context, keycloakClient *KeycloakClient) error { if mapper.ClientId == "" && mapper.ClientScopeId == "" { return fmt.Errorf("validation error: one of ClientId or ClientScopeId must be set") } diff --git a/provider/provider.go b/provider/provider.go index da3a532c6..68a3576e2 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -78,6 +78,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider { "keycloak_saml_client_default_scopes": resourceKeycloakSamlClientDefaultScopes(), "keycloak_generic_client_protocol_mapper": resourceKeycloakGenericClientProtocolMapper(), "keycloak_generic_client_role_mapper": resourceKeycloakGenericClientRoleMapper(), + "keycloak_generic_protocol_mapper": resourceKeycloakGenericProtocolMapper(), "keycloak_saml_user_attribute_protocol_mapper": resourceKeycloakSamlUserAttributeProtocolMapper(), "keycloak_saml_user_property_protocol_mapper": resourceKeycloakSamlUserPropertyProtocolMapper(), "keycloak_saml_script_protocol_mapper": resourceKeycloakSamlScriptProtocolMapper(), diff --git a/provider/resource_keycloak_generic_client_protocol_mapper.go b/provider/resource_keycloak_generic_client_protocol_mapper.go index d06922ad7..076305b67 100644 --- a/provider/resource_keycloak_generic_client_protocol_mapper.go +++ b/provider/resource_keycloak_generic_client_protocol_mapper.go @@ -22,6 +22,7 @@ func resourceKeycloakGenericClientProtocolMapper() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: genericProtocolMapperImport, }, + DeprecationMessage: "please use keycloak_generic_protocol_mapper instead", Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -70,7 +71,7 @@ func resourceKeycloakGenericClientProtocolMapper() *schema.Resource { } } -func mapFromDataToGenericClientProtocolMapper(data *schema.ResourceData) *keycloak.GenericClientProtocolMapper { +func mapFromDataToGenericClientProtocolMapper(data *schema.ResourceData) *keycloak.GenericProtocolMapper { config := make(map[string]string) if v, ok := data.GetOk("config"); ok { for key, value := range v.(map[string]interface{}) { @@ -78,7 +79,7 @@ func mapFromDataToGenericClientProtocolMapper(data *schema.ResourceData) *keyclo } } - return &keycloak.GenericClientProtocolMapper{ + return &keycloak.GenericProtocolMapper{ ClientId: data.Get("client_id").(string), ClientScopeId: data.Get("client_scope_id").(string), Config: config, @@ -90,7 +91,7 @@ func mapFromDataToGenericClientProtocolMapper(data *schema.ResourceData) *keyclo } } -func mapFromGenericClientProtocolMapperToData(data *schema.ResourceData, mapper *keycloak.GenericClientProtocolMapper) { +func mapFromGenericClientProtocolMapperToData(data *schema.ResourceData, mapper *keycloak.GenericProtocolMapper) { data.SetId(mapper.Id) if mapper.ClientId != "" { data.Set("client_id", mapper.ClientId) @@ -114,7 +115,7 @@ func resourceKeycloakGenericClientProtocolMapperCreate(ctx context.Context, data return diag.FromErr(err) } - err = keycloakClient.NewGenericClientProtocolMapper(ctx, genericClientProtocolMapper) + err = keycloakClient.NewGenericProtocolMapper(ctx, genericClientProtocolMapper) if err != nil { return diag.FromErr(err) } @@ -131,7 +132,7 @@ func resourceKeycloakGenericClientProtocolMapperRead(ctx context.Context, data * clientScopeId := data.Get("client_scope_id").(string) id := data.Id() - resource, err := keycloakClient.GetGenericClientProtocolMapper(ctx, realmId, clientId, clientScopeId, id) + resource, err := keycloakClient.GetGenericProtocolMapper(ctx, realmId, clientId, clientScopeId, id) if err != nil { return handleNotFoundError(ctx, err, data) } @@ -146,7 +147,7 @@ func resourceKeycloakGenericClientProtocolMapperUpdate(ctx context.Context, data resource := mapFromDataToGenericClientProtocolMapper(data) - err := keycloakClient.UpdateGenericClientProtocolMapper(ctx, resource) + err := keycloakClient.UpdateGenericProtocolMapper(ctx, resource) if err != nil { return diag.FromErr(err) } @@ -164,5 +165,5 @@ func resourceKeycloakGenericClientProtocolMapperDelete(ctx context.Context, data clientScopeId := data.Get("client_scope_id").(string) id := data.Id() - return diag.FromErr(keycloakClient.DeleteGenericClientProtocolMapper(ctx, realmId, clientId, clientScopeId, id)) + return diag.FromErr(keycloakClient.DeleteGenericProtocolMapper(ctx, realmId, clientId, clientScopeId, id)) } diff --git a/provider/resource_keycloak_generic_client_protocol_mapper_test.go b/provider/resource_keycloak_generic_client_protocol_mapper_test.go index 8e4e7a772..e42a70b1f 100644 --- a/provider/resource_keycloak_generic_client_protocol_mapper_test.go +++ b/provider/resource_keycloak_generic_client_protocol_mapper_test.go @@ -131,7 +131,7 @@ func testAccKeycloakGenericClientProtocolMapperDestroy() resource.TestCheckFunc } } -func getGenericClientProtocolMapperUsingState(state *terraform.State, resourceName string) (*keycloak.GenericClientProtocolMapper, error) { +func getGenericClientProtocolMapperUsingState(state *terraform.State, resourceName string) (*keycloak.GenericProtocolMapper, error) { rs, ok := state.RootModule().Resources[resourceName] if !ok { return nil, fmt.Errorf("resource not found in TF state: %s ", resourceName) @@ -142,7 +142,7 @@ func getGenericClientProtocolMapperUsingState(state *terraform.State, resourceNa clientId := rs.Primary.Attributes["client_id"] clientScopeId := rs.Primary.Attributes["client_scope_id"] - return keycloakClient.GetGenericClientProtocolMapper(testCtx, realmId, clientId, clientScopeId, mapperId) + return keycloakClient.GetGenericProtocolMapper(testCtx, realmId, clientId, clientScopeId, mapperId) } func testKeycloakGenericClientProtocolMapper_basic_client(clientId string, mapperName string) string { diff --git a/provider/resource_keycloak_generic_protocol_mapper.go b/provider/resource_keycloak_generic_protocol_mapper.go new file mode 100644 index 000000000..c6e26d35b --- /dev/null +++ b/provider/resource_keycloak_generic_protocol_mapper.go @@ -0,0 +1,168 @@ +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakGenericProtocolMapper() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceKeycloakGenericProtocolMapperCreate, + ReadContext: resourceKeycloakGenericProtocolMapperRead, + DeleteContext: resourceKeycloakGenericProtocolMapperDelete, + UpdateContext: resourceKeycloakGenericProtocolMapperUpdate, + // import a mapper tied to a client: + // {{realmId}}/client/{{clientId}}/{{protocolMapperId}} + // or a client scope: + // {{realmId}}/client-scope/{{clientScopeId}}/{{protocolMapperId}} + Importer: &schema.ResourceImporter{ + StateContext: genericProtocolMapperImport, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: 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"}, + }, + "protocol": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The protocol of the client (openid-connect / saml).", + ValidateFunc: validation.StringInSlice([]string{"openid-connect", "saml"}, false), + }, + "protocol_mapper": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The type of the protocol mapper.", + }, + "config": { + Type: schema.TypeMap, + Required: true, + }, + }, + } +} + +func mapFromDataToGenericProtocolMapper(data *schema.ResourceData) *keycloak.GenericProtocolMapper { + config := make(map[string]string) + if v, ok := data.GetOk("config"); ok { + for key, value := range v.(map[string]interface{}) { + config[key] = value.(string) + } + } + + return &keycloak.GenericProtocolMapper{ + ClientId: data.Get("client_id").(string), + ClientScopeId: data.Get("client_scope_id").(string), + Config: config, + Id: data.Id(), + Name: data.Get("name").(string), + Protocol: data.Get("protocol").(string), + ProtocolMapper: data.Get("protocol_mapper").(string), + RealmId: data.Get("realm_id").(string), + } +} + +func mapFromGenericProtocolMapperToData(data *schema.ResourceData, mapper *keycloak.GenericProtocolMapper) { + data.SetId(mapper.Id) + if mapper.ClientId != "" { + data.Set("client_id", mapper.ClientId) + } else { + data.Set("client_scope_id", mapper.ClientScopeId) + } + data.Set("config", mapper.Config) + data.Set("name", mapper.Name) + data.Set("protocol", mapper.Protocol) + data.Set("protocol_mapper", mapper.ProtocolMapper) + data.Set("realm_id", mapper.RealmId) +} + +func resourceKeycloakGenericProtocolMapperCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + keycloakClient := meta.(*keycloak.KeycloakClient) + + genericProtocolMapper := mapFromDataToGenericProtocolMapper(data) + + err := genericProtocolMapper.Validate(ctx, keycloakClient) + if err != nil { + return diag.FromErr(err) + } + + err = keycloakClient.NewGenericProtocolMapper(ctx, genericProtocolMapper) + if err != nil { + return diag.FromErr(err) + } + mapFromGenericProtocolMapperToData(data, genericProtocolMapper) + + return resourceKeycloakGenericProtocolMapperRead(ctx, data, meta) +} + +func resourceKeycloakGenericProtocolMapperRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realmId := data.Get("realm_id").(string) + clientId := data.Get("client_id").(string) + clientScopeId := data.Get("client_scope_id").(string) + id := data.Id() + + resource, err := keycloakClient.GetGenericProtocolMapper(ctx, realmId, clientId, clientScopeId, id) + if err != nil { + return handleNotFoundError(ctx, err, data) + } + + mapFromGenericProtocolMapperToData(data, resource) + + return nil +} + +func resourceKeycloakGenericProtocolMapperUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + keycloakClient := meta.(*keycloak.KeycloakClient) + + resource := mapFromDataToGenericProtocolMapper(data) + + err := keycloakClient.UpdateGenericProtocolMapper(ctx, resource) + if err != nil { + return diag.FromErr(err) + } + + mapFromGenericProtocolMapperToData(data, resource) + + return nil +} + +func resourceKeycloakGenericProtocolMapperDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realmId := data.Get("realm_id").(string) + clientId := data.Get("client_id").(string) + clientScopeId := data.Get("client_scope_id").(string) + id := data.Id() + + return diag.FromErr(keycloakClient.DeleteGenericProtocolMapper(ctx, realmId, clientId, clientScopeId, id)) +} diff --git a/provider/resource_keycloak_generic_protocol_mapper_test.go b/provider/resource_keycloak_generic_protocol_mapper_test.go new file mode 100644 index 000000000..81cbec373 --- /dev/null +++ b/provider/resource_keycloak_generic_protocol_mapper_test.go @@ -0,0 +1,259 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccKeycloakGenericProtocolMapper_basicClient(t *testing.T) { + t.Parallel() + + clientId := acctest.RandomWithPrefix("tf-acc") + mapperName := acctest.RandomWithPrefix("tf-acc") + + resourceName := "keycloak_generic_protocol_mapper.client_protocol_mapper" + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccKeycloakGenericProtocolMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakGenericProtocolMapper_basic_client(clientId, mapperName), + Check: testKeycloakGenericProtocolMapperExists(resourceName), + }, + }, + }) +} + +func TestAccKeycloakGenericProtocolMapper_basicClientScope(t *testing.T) { + t.Parallel() + + clientScopeId := acctest.RandomWithPrefix("tf-acc") + mapperName := acctest.RandomWithPrefix("tf-acc") + + resourceName := "keycloak_generic_protocol_mapper.client_protocol_mapper" + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccKeycloakGenericProtocolMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakGenericProtocolMapper_basic_clientScope(clientScopeId, mapperName), + Check: testKeycloakGenericProtocolMapperExists(resourceName), + }, + }, + }) +} + +func TestAccKeycloakGenericProtocolMapper_import(t *testing.T) { + t.Parallel() + + clientId := acctest.RandomWithPrefix("tf-acc") + mapperName := acctest.RandomWithPrefix("tf-acc") + + resourceName := "keycloak_generic_protocol_mapper.client_protocol_mapper" + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccKeycloakGenericProtocolMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakGenericProtocolMapper_import(clientId, mapperName), + Check: testKeycloakGenericProtocolMapperExists(resourceName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: getGenericProtocolMapperIdForClient(resourceName), + }, + }, + }) +} + +func TestAccKeycloakGenericProtocolMapper_update(t *testing.T) { + t.Parallel() + + clientId := acctest.RandomWithPrefix("tf-acc") + mapperName := acctest.RandomWithPrefix("tf-acc") + + resourceName := "keycloak_generic_protocol_mapper.client_protocol_mapper" + + oldAttributeName := acctest.RandomWithPrefix("tf-acc") + oldAttributeValue := acctest.RandomWithPrefix("tf-acc") + newAttributeName := acctest.RandomWithPrefix("tf-acc") + newAttributeValue := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccKeycloakGenericProtocolMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakGenericProtocolMapper_update(clientId, mapperName, oldAttributeName, oldAttributeValue), + Check: testKeycloakGenericProtocolMapperExists(resourceName), + }, + { + Config: testKeycloakGenericProtocolMapper_update(clientId, mapperName, newAttributeName, newAttributeValue), + Check: resource.ComposeTestCheckFunc( + testKeycloakGenericProtocolMapperExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "config.attribute.name", newAttributeName), + resource.TestCheckResourceAttr(resourceName, "config.attribute.value", newAttributeValue)), + }, + }, + }) +} + +func testAccKeycloakGenericProtocolMapperDestroy() resource.TestCheckFunc { + return func(state *terraform.State) error { + for resourceName, rs := range state.RootModule().Resources { + if rs.Type != "keycloak_generic_protocol_mapper" { + continue + } + + mapper, _ := getGenericProtocolMapperUsingState(state, resourceName) + + if mapper != nil { + return fmt.Errorf("generic client protocol mapper with id %s still exists", rs.Primary.ID) + } + } + + return nil + } +} + +func getGenericProtocolMapperUsingState(state *terraform.State, resourceName string) (*keycloak.GenericProtocolMapper, error) { + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found in TF state: %s ", resourceName) + } + + mapperId := rs.Primary.ID + realmId := rs.Primary.Attributes["realm_id"] + clientId := rs.Primary.Attributes["client_id"] + clientScopeId := rs.Primary.Attributes["client_scope_id"] + + return keycloakClient.GetGenericProtocolMapper(testCtx, realmId, clientId, clientScopeId, mapperId) +} + +func testKeycloakGenericProtocolMapper_basic_client(clientId string, mapperName string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_client" "saml_client" { + realm_id = data.keycloak_realm.realm.id + client_id = "%s" +} + +resource "keycloak_generic_protocol_mapper" "client_protocol_mapper" { + client_id = keycloak_saml_client.saml_client.id + name = "%s" + protocol = "saml" + protocol_mapper = "saml-hardcode-attribute-mapper" + realm_id = data.keycloak_realm.realm.id + config = { + "attribute.name" = "name" + "attribute.nameformat" = "Basic" + "attribute.value" = "value" + "friendly.name" = "%s" + } +}`, testAccRealm.Realm, clientId, mapperName, mapperName) +} + +func testKeycloakGenericProtocolMapper_basic_clientScope(clientScopeId string, mapperName string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_openid_client_scope" "client_scope" { + name = "%s" + realm_id = data.keycloak_realm.realm.id +} + +resource "keycloak_generic_protocol_mapper" "client_protocol_mapper" { + name = "%s" + realm_id = data.keycloak_realm.realm.id + client_scope_id = keycloak_openid_client_scope.client_scope.id + protocol = "openid-connect" + protocol_mapper = "oidc-usermodel-property-mapper" + config = { + "user.attribute" = "foo" + "claim.name" = "bar" + } +}`, testAccRealm.Realm, clientScopeId, mapperName) +} + +func testKeycloakGenericProtocolMapper_import(clientId string, mapperName string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_client" "saml_client" { + realm_id = data.keycloak_realm.realm.id + client_id = "%s" +} + +resource "keycloak_generic_protocol_mapper" "client_protocol_mapper" { + client_id = keycloak_saml_client.saml_client.id + name = "%s" + protocol = "saml" + protocol_mapper = "saml-hardcode-attribute-mapper" + realm_id = data.keycloak_realm.realm.id + config = { + "attribute.name" = "name" + "attribute.nameformat" = "Basic" + "attribute.value" = "value" + "friendly.name" = "%s" + } +}`, testAccRealm.Realm, clientId, mapperName, mapperName) +} + +func testKeycloakGenericProtocolMapper_update(clientId string, mapperName string, attributeName string, attributeValue string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_client" "saml_client" { + realm_id = data.keycloak_realm.realm.id + client_id = "%s" +} + +resource "keycloak_generic_protocol_mapper" "client_protocol_mapper" { + client_id = keycloak_saml_client.saml_client.id + name = "%s" + protocol = "saml" + protocol_mapper = "saml-hardcode-attribute-mapper" + realm_id = data.keycloak_realm.realm.id + config = { + "attribute.name" = "%s" + "attribute.nameformat" = "Basic" + "attribute.value" = "%s" + "friendly.name" = "%s" + } +}`, testAccRealm.Realm, clientId, mapperName, attributeName, attributeValue, mapperName) +} + +func testKeycloakGenericProtocolMapperExists(resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + _, err := getGenericProtocolMapperUsingState(state, resourceName) + if err != nil { + return err + } + + return nil + } +}