From f761899ef20673945163bf191ec9371fdb29a89b Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 09:39:44 -0500 Subject: [PATCH 1/9] misc changes for extra_config with keycloak_openid_client resource --- docs/resources/openid_client.md | 10 ++-- ...data_source_keycloak_openid_client_test.go | 48 +++++++++---------- .../resource_keycloak_openid_client_test.go | 17 +++++++ 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/docs/resources/openid_client.md b/docs/resources/openid_client.md index e9dbb7741..0a9831192 100644 --- a/docs/resources/openid_client.md +++ b/docs/resources/openid_client.md @@ -31,11 +31,11 @@ resource "keycloak_openid_client" "openid_client" { ] login_theme = "keycloak" - + extra_config = { - "key1" = "value1" - "key2" = "value2" - } + "key1" = "value1" + "key2" = "value2" + } } ``` @@ -86,7 +86,7 @@ is set to `true`. - `backchannel_logout_url` - (Optional) The URL that will cause the client to log itself out when a logout request is sent to this realm. If omitted, no logout request will be sent to the client is this case. - `backchannel_logout_session_required` - (Optional) When `true`, a sid (session ID) claim will be included in the logout token when the backchannel logout URL is used. Defaults to `true`. - `backchannel_logout_revoke_offline_sessions` - (Optional) Specifying whether a "revoke_offline_access" event is included in the Logout Token when the Backchannel Logout URL is used. Keycloak will revoke offline sessions when receiving a Logout Token with this event. -- `extra_config` - (Optional) A map of key/value pairs to add extra configuration attributes to this client. This can be used for custom attributes, or to add configuration attributes that is not yet supported by this Terraform provider. Use this attribute at your own risk, as s may conflict with top-level configuration attributes in future provider updates. +- `extra_config` - (Optional) A map of key/value pairs to add extra configuration attributes to this client. This can be used for custom attributes, or to add configuration attributes that are not yet supported by this Terraform provider. Use this attribute at your own risk, as it may conflict with top-level configuration attributes in future provider updates. ## Attributes Reference diff --git a/provider/data_source_keycloak_openid_client_test.go b/provider/data_source_keycloak_openid_client_test.go index 2890a77e1..76f8878ff 100644 --- a/provider/data_source_keycloak_openid_client_test.go +++ b/provider/data_source_keycloak_openid_client_test.go @@ -40,6 +40,26 @@ func TestAccKeycloakDataSourceOpenidClient_basic(t *testing.T) { }) } +func TestAccKeycloakDataSourceOpenidClient_extraConfig(t *testing.T) { + t.Parallel() + clientId := acctest.RandomWithPrefix("tf-acc-test-extra-config") + dataSourceName := "data.keycloak_openid_client.test_extra_config" + resourceName := "keycloak_openid_client.test_extra_config" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccKeycloakOpenidClientConfig_extraConfig(clientId), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "key1", resourceName, "value1"), + ), + }, + }, + }) +} + func testAccKeycloakOpenidClientConfig(clientId string) string { return fmt.Sprintf(` data "keycloak_realm" "realm" { @@ -78,33 +98,13 @@ data "keycloak_openid_client" "test" { `, testAccRealm.Realm, clientId, clientId) } -func TestAccKeycloakDataSourceOpenidClient_extraConfig(t *testing.T) { - t.Parallel() - clientId := acctest.RandomWithPrefix("tf-acc-test-extra-config") - dataSourceName := "data.keycloak_openid_client.test-extra-config" - resourceName := "keycloak_openid_client.test-extra-config" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccKeycloakOpenidClientConfig_extraConfig(clientId), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrPair(dataSourceName, "key1", resourceName, "value1"), - ), - }, - }, - }) -} - func testAccKeycloakOpenidClientConfig_extraConfig(clientId string) string { return fmt.Sprintf(` data "keycloak_realm" "realm" { realm = "%s" } -resource "keycloak_openid_client" "test-extra-config" { +resource "keycloak_openid_client" "test_extra_config" { name = "%s" client_id = "%s" realm_id = data.keycloak_realm.realm.id @@ -115,12 +115,12 @@ resource "keycloak_openid_client" "test-extra-config" { } } -data "keycloak_openid_client" "test-extra-config" { +data "keycloak_openid_client" "test_extra_config" { realm_id = data.keycloak_realm.realm.id - client_id = keycloak_openid_client.test-extra-config.client_id + client_id = keycloak_openid_client.test_extra_config.client_id depends_on = [ - keycloak_openid_client.test-extra-config, + keycloak_openid_client.test_extra_config, ] } `, testAccRealm.Realm, clientId, clientId) diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go index 193d4882f..6ef074a9c 100644 --- a/provider/resource_keycloak_openid_client_test.go +++ b/provider/resource_keycloak_openid_client_test.go @@ -539,6 +539,23 @@ func TestAccKeycloakOpenidClient_extraConfig(t *testing.T) { }) } +func TestAccKeycloakOpenidClient_extraConfigInvalid(t *testing.T) { + t.Parallel() + clientId := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOpenidClientDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOpenidClient_extraConfig(clientId, "login_theme", "keycloak"), + ExpectError: regexp.MustCompile(`extra_config key "login_theme" is not allowed`), + }, + }, + }) +} + func testAccCheckKeycloakOpenidClientExistsWithCorrectProtocol(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { client, err := getOpenidClientFromState(s, resourceName) From 17a01dd89b8deb6c638d9327e23fbf427752e834 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 12:15:17 -0500 Subject: [PATCH 2/9] better debug logs for http requests to keycloak --- keycloak/keycloak_client.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/keycloak/keycloak_client.go b/keycloak/keycloak_client.go index 5c9a82185..3ad996975 100644 --- a/keycloak/keycloak_client.go +++ b/keycloak/keycloak_client.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/cookiejar" "net/url" + "os" "strings" "time" @@ -28,6 +29,7 @@ type KeycloakClient struct { userAgent string version *version.Version additionalHeaders map[string]string + debug bool } type ClientCredentials struct { @@ -109,6 +111,12 @@ func NewKeycloakClient(url, basePath, clientId, clientSecret, realm, username, p } } + if tfLog, ok := os.LookupEnv("TF_LOG"); ok { + if tfLog == "DEBUG" { + keycloakClient.debug = true + } + } + return &keycloakClient, nil } @@ -391,7 +399,7 @@ func (keycloakClient *KeycloakClient) sendRaw(path string, requestBody []byte) ( func (keycloakClient *KeycloakClient) post(path string, requestBody interface{}) ([]byte, string, error) { resourceUrl := keycloakClient.baseUrl + apiUrl + path - payload, err := json.Marshal(requestBody) + payload, err := keycloakClient.marshal(requestBody) if err != nil { return nil, "", err } @@ -409,7 +417,7 @@ func (keycloakClient *KeycloakClient) post(path string, requestBody interface{}) func (keycloakClient *KeycloakClient) put(path string, requestBody interface{}) error { resourceUrl := keycloakClient.baseUrl + apiUrl + path - payload, err := json.Marshal(requestBody) + payload, err := keycloakClient.marshal(requestBody) if err != nil { return err } @@ -433,7 +441,7 @@ func (keycloakClient *KeycloakClient) delete(path string, requestBody interface{ ) if requestBody != nil { - payload, err = json.Marshal(requestBody) + payload, err = keycloakClient.marshal(requestBody) if err != nil { return err } @@ -448,3 +456,11 @@ func (keycloakClient *KeycloakClient) delete(path string, requestBody interface{ return err } + +func (keycloakClient *KeycloakClient) marshal(requestBody interface{}) ([]byte, error) { + if keycloakClient.debug { + return json.MarshalIndent(requestBody, "", " ") + } + + return json.Marshal(requestBody) +} From b61b654e570075240542e4f21688dcce692d9020 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 12:15:34 -0500 Subject: [PATCH 3/9] move extra_config code to another file --- keycloak/extra_config.go | 62 +++++++++++++++++++ keycloak/openid_client.go | 53 +--------------- provider/extra_config.go | 41 ++++++++++++ provider/resource_keycloak_openid_client.go | 11 +--- .../resource_keycloak_openid_client_test.go | 52 +++++++++++++--- 5 files changed, 151 insertions(+), 68 deletions(-) create mode 100644 keycloak/extra_config.go create mode 100644 provider/extra_config.go diff --git a/keycloak/extra_config.go b/keycloak/extra_config.go new file mode 100644 index 000000000..56c484a41 --- /dev/null +++ b/keycloak/extra_config.go @@ -0,0 +1,62 @@ +package keycloak + +import ( + "encoding/json" + "reflect" + "strconv" + "strings" +) + +func unmarshalExtraConfig(data []byte, reflectValue reflect.Value, extraConfig *map[string]interface{}) error { + err := json.Unmarshal(data, extraConfig) + if err != nil { + return err + } + + for i := 0; i < reflectValue.NumField(); i++ { + structField := reflectValue.Type().Field(i) + jsonKey := strings.Split(structField.Tag.Get("json"), ",")[0] + if jsonKey != "-" { + configValue, ok := (*extraConfig)[jsonKey] + if ok { + field := reflectValue.FieldByName(structField.Name) + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.String { + field.SetString(configValue.(string)) + } else if field.Kind() == reflect.Bool { + boolVal, err := strconv.ParseBool(configValue.(string)) + if err == nil { + field.Set(reflect.ValueOf(KeycloakBoolQuoted(boolVal))) + } + } + delete(*extraConfig, jsonKey) + } + } + } + } + + return nil +} + +func marshalExtraConfig(reflectValue reflect.Value, extraConfig map[string]interface{}) ([]byte, error) { + out := map[string]interface{}{} + + for k, v := range extraConfig { + out[k] = v + } + + for i := 0; i < reflectValue.NumField(); i++ { + jsonKey := strings.Split(reflectValue.Type().Field(i).Tag.Get("json"), ",")[0] + if jsonKey != "-" { + field := reflectValue.Field(i) + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.String { + out[jsonKey] = field.String() + } else if field.Kind() == reflect.Bool { + out[jsonKey] = KeycloakBoolQuoted(field.Bool()) + } + } + } + } + return json.Marshal(out) +} diff --git a/keycloak/openid_client.go b/keycloak/openid_client.go index 9fb77c9c0..9502412cd 100644 --- a/keycloak/openid_client.go +++ b/keycloak/openid_client.go @@ -1,11 +1,8 @@ package keycloak import ( - "encoding/json" "fmt" "reflect" - "strconv" - "strings" ) type OpenidClientRole struct { @@ -354,55 +351,9 @@ func (keycloakClient *KeycloakClient) DetachOpenidClientOptionalScopes(realmId, } func (f *OpenidClientAttributes) UnmarshalJSON(data []byte) error { - f.ExtraConfig = map[string]interface{}{} - err := json.Unmarshal(data, &f.ExtraConfig) - if err != nil { - return err - } - v := reflect.ValueOf(f).Elem() - for i := 0; i < v.NumField(); i++ { - structField := v.Type().Field(i) - jsonKey := strings.Split(structField.Tag.Get("json"), ",")[0] - if jsonKey != "-" { - value, ok := f.ExtraConfig[jsonKey] - if ok { - field := v.FieldByName(structField.Name) - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - field.SetString(value.(string)) - } else if field.Kind() == reflect.Bool { - boolVal, err := strconv.ParseBool(value.(string)) - if err == nil { - field.Set(reflect.ValueOf(KeycloakBoolQuoted(boolVal))) - } - } - delete(f.ExtraConfig, jsonKey) - } - } - } - } - return nil + return unmarshalExtraConfig(data, reflect.ValueOf(f).Elem(), &f.ExtraConfig) } func (f *OpenidClientAttributes) MarshalJSON() ([]byte, error) { - out := map[string]interface{}{} - - for k, v := range f.ExtraConfig { - out[k] = v - } - v := reflect.ValueOf(f).Elem() - for i := 0; i < v.NumField(); i++ { - jsonKey := strings.Split(v.Type().Field(i).Tag.Get("json"), ",")[0] - if jsonKey != "-" { - field := v.Field(i) - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - out[jsonKey] = field.String() - } else if field.Kind() == reflect.Bool { - out[jsonKey] = KeycloakBoolQuoted(field.Bool()) - } - } - } - } - return json.Marshal(out) + return marshalExtraConfig(reflect.ValueOf(f).Elem(), f.ExtraConfig) } diff --git a/provider/extra_config.go b/provider/extra_config.go new file mode 100644 index 000000000..1121861bf --- /dev/null +++ b/provider/extra_config.go @@ -0,0 +1,41 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func getExtraConfigFromData(data *schema.ResourceData) map[string]interface{} { + extraConfig := map[string]interface{}{} + if v, ok := data.GetOk("extra_config"); ok { + for key, value := range v.(map[string]interface{}) { + extraConfig[key] = value + } + + if data.HasChange("extra_config") && !data.IsNewResource() { + oldConfig, newConfig := data.GetChange("extra_config") + newConfigMap := newConfig.(map[string]interface{}) + + for oldKey := range oldConfig.(map[string]interface{}) { + if _, ok := newConfigMap[oldKey]; !ok { + extraConfig[oldKey] = "" + } + } + } + } + + return extraConfig +} + +func setExtraConfigData(data *schema.ResourceData, extraConfig map[string]interface{}) { + c := map[string]interface{}{} + + for k, v := range extraConfig { + if s, ok := v.(string); ok && s == "" { + continue + } + + c[k] = v + } + + data.Set("extra_config", c) +} diff --git a/provider/resource_keycloak_openid_client.go b/provider/resource_keycloak_openid_client.go index b2f0ddfe1..5ff23046f 100644 --- a/provider/resource_keycloak_openid_client.go +++ b/provider/resource_keycloak_openid_client.go @@ -300,13 +300,6 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient, } } - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } - openidClient := &keycloak.OpenidClient{ Id: data.Id(), ClientId: data.Get("client_id").(string), @@ -333,7 +326,7 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient, BackchannelLogoutUrl: data.Get("backchannel_logout_url").(string), BackchannelLogoutRevokeOfflineTokens: keycloak.KeycloakBoolQuoted(data.Get("backchannel_logout_session_required").(bool)), BackchannelLogoutSessionRequired: keycloak.KeycloakBoolQuoted(data.Get("backchannel_logout_revoke_offline_sessions").(bool)), - ExtraConfig: extraConfig, + ExtraConfig: getExtraConfigFromData(data), }, ValidRedirectUris: validRedirectUris, WebOrigins: webOrigins, @@ -429,7 +422,7 @@ func setOpenidClientData(keycloakClient *keycloak.KeycloakClient, data *schema.R data.Set("backchannel_logout_url", client.Attributes.BackchannelLogoutUrl) data.Set("backchannel_logout_session_required", client.Attributes.BackchannelLogoutRevokeOfflineTokens) data.Set("backchannel_logout_revoke_offline_sessions", client.Attributes.BackchannelLogoutSessionRequired) - data.Set("extra_config", client.Attributes.ExtraConfig) + setExtraConfigData(data, client.Attributes.ExtraConfig) if client.AuthorizationServicesEnabled { data.Set("resource_server_id", client.Id) diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go index 6ef074a9c..ec3d19b51 100644 --- a/provider/resource_keycloak_openid_client_test.go +++ b/provider/resource_keycloak_openid_client_test.go @@ -3,6 +3,7 @@ package provider import ( "fmt" "regexp" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -532,8 +533,23 @@ func TestAccKeycloakOpenidClient_extraConfig(t *testing.T) { CheckDestroy: testAccCheckKeycloakOpenidClientDestroy(), Steps: []resource.TestStep{ { - Config: testKeycloakOpenidClient_extraConfig(clientId, "key1", "value1"), - Check: testAccCheckKeycloakOpenidClientExtraConfig("keycloak_openid_client.client", "key1", "value1"), + Config: testKeycloakOpenidClient_extraConfig(clientId, map[string]string{ + "key1": "value1", + "key2": "value2", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakOpenidClientExtraConfig("keycloak_openid_client.client", "key1", "value1"), + testAccCheckKeycloakOpenidClientExtraConfig("keycloak_openid_client.client", "key2", "value2"), + ), + }, + { + Config: testKeycloakOpenidClient_extraConfig(clientId, map[string]string{ + "key2": "value2", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakOpenidClientExtraConfig("keycloak_openid_client.client", "key2", "value2"), + testAccCheckKeycloakOpenidClientExtraConfigMissing("keycloak_openid_client.client", "key1"), + ), }, }, }) @@ -549,7 +565,7 @@ func TestAccKeycloakOpenidClient_extraConfigInvalid(t *testing.T) { CheckDestroy: testAccCheckKeycloakOpenidClientDestroy(), Steps: []resource.TestStep{ { - Config: testKeycloakOpenidClient_extraConfig(clientId, "login_theme", "keycloak"), + Config: testKeycloakOpenidClient_extraConfig(clientId, map[string]string{"login_theme": "keycloak"}), ExpectError: regexp.MustCompile(`extra_config key "login_theme" is not allowed`), }, }, @@ -823,6 +839,22 @@ func testAccCheckKeycloakOpenidClientExtraConfig(resourceName string, key string } } +// check that a particular extra config key is missing +func testAccCheckKeycloakOpenidClientExtraConfigMissing(resourceName string, key string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client, err := getOpenidClientFromState(s, resourceName) + if err != nil { + return err + } + + if _, ok := client.Attributes.ExtraConfig[key]; ok { + return fmt.Errorf("expected openid client to not have attribute %v", key) + } + + return nil + } +} + func getOpenidClientFromState(s *terraform.State, resourceName string) (*keycloak.OpenidClient, error) { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -1179,7 +1211,13 @@ resource "keycloak_openid_client" "client" { `, testAccRealm.Realm, clientId, useRefreshTokens) } -func testKeycloakOpenidClient_extraConfig(clientId string, key string, value string) string { +func testKeycloakOpenidClient_extraConfig(clientId string, extraConfig map[string]string) string { + var sb strings.Builder + sb.WriteString("{\n") + for k, v := range extraConfig { + sb.WriteString(fmt.Sprintf("\t\t\"%s\" = \"%s\"\n", k, v)) + } + sb.WriteString("}") return fmt.Sprintf(` data "keycloak_realm" "realm" { @@ -1190,9 +1228,7 @@ resource "keycloak_openid_client" "client" { client_id = "%s" realm_id = data.keycloak_realm.realm.id access_type = "CONFIDENTIAL" - extra_config = { - "%s" = "%s" - } + extra_config = %s } - `, testAccRealm.Realm, clientId, key, value) + `, testAccRealm.Realm, clientId, sb.String()) } From dce16eb5abe25050c9e899e51f57f71056c55848 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 13:04:45 -0500 Subject: [PATCH 4/9] more comments for extra_config logic --- provider/extra_config.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/provider/extra_config.go b/provider/extra_config.go index 1121861bf..a88fb5341 100644 --- a/provider/extra_config.go +++ b/provider/extra_config.go @@ -11,6 +11,9 @@ func getExtraConfigFromData(data *schema.ResourceData) map[string]interface{} { extraConfig[key] = value } + // check if extra config attribute has been deleted. + // it's not enough to simply unset the attribute - we have to explicitly set + // it to empty string in order to remove this on the Keycloak side if data.HasChange("extra_config") && !data.IsNewResource() { oldConfig, newConfig := data.GetChange("extra_config") newConfigMap := newConfig.(map[string]interface{}) @@ -29,6 +32,7 @@ func getExtraConfigFromData(data *schema.ResourceData) map[string]interface{} { func setExtraConfigData(data *schema.ResourceData, extraConfig map[string]interface{}) { c := map[string]interface{}{} + // when saving back to state, don't persist empty attributes that we're trying to remove from Keycloak for k, v := range extraConfig { if s, ok := v.(string); ok && s == "" { continue From 3212953879a938dc723e0d282f8c01d6af22ad4d Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 13:07:44 -0500 Subject: [PATCH 5/9] consume new extra config helper functions for identity providers --- keycloak/identity_provider.go | 65 +++---------------- keycloak/identity_provider_mapper.go | 53 +-------------- .../generic_keycloak_identity_provider.go | 16 ++--- 3 files changed, 14 insertions(+), 120 deletions(-) diff --git a/keycloak/identity_provider.go b/keycloak/identity_provider.go index ef220cfe8..24827a037 100644 --- a/keycloak/identity_provider.go +++ b/keycloak/identity_provider.go @@ -1,12 +1,9 @@ package keycloak import ( - "encoding/json" "fmt" "log" "reflect" - "strconv" - "strings" ) type IdentityProviderConfig struct { @@ -69,60 +66,6 @@ type IdentityProvider struct { Config *IdentityProviderConfig `json:"config"` } -func (f *IdentityProviderConfig) UnmarshalJSON(data []byte) error { - f.ExtraConfig = map[string]interface{}{} - err := json.Unmarshal(data, &f.ExtraConfig) - if err != nil { - return err - } - v := reflect.ValueOf(f).Elem() - for i := 0; i < v.NumField(); i++ { - structField := v.Type().Field(i) - jsonKey := strings.Split(structField.Tag.Get("json"), ",")[0] - if jsonKey != "-" { - value, ok := f.ExtraConfig[jsonKey] - if ok { - field := v.FieldByName(structField.Name) - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - field.SetString(value.(string)) - } else if field.Kind() == reflect.Bool { - boolVal, err := strconv.ParseBool(value.(string)) - if err == nil { - field.Set(reflect.ValueOf(KeycloakBoolQuoted(boolVal))) - } - } - delete(f.ExtraConfig, jsonKey) - } - } - } - } - return nil -} - -func (f *IdentityProviderConfig) MarshalJSON() ([]byte, error) { - out := map[string]interface{}{} - - for k, v := range f.ExtraConfig { - out[k] = v - } - v := reflect.ValueOf(f).Elem() - for i := 0; i < v.NumField(); i++ { - jsonKey := strings.Split(v.Type().Field(i).Tag.Get("json"), ",")[0] - if jsonKey != "-" { - field := v.Field(i) - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - out[jsonKey] = field.String() - } else if field.Kind() == reflect.Bool { - out[jsonKey] = KeycloakBoolQuoted(field.Bool()) - } - } - } - } - return json.Marshal(out) -} - func (keycloakClient *KeycloakClient) NewIdentityProvider(identityProvider *IdentityProvider) error { log.Printf("[WARN] Realm: %s", identityProvider.Realm) _, _, err := keycloakClient.post(fmt.Sprintf("/realms/%s/identity-provider/instances", identityProvider.Realm), identityProvider) @@ -152,3 +95,11 @@ func (keycloakClient *KeycloakClient) UpdateIdentityProvider(identityProvider *I func (keycloakClient *KeycloakClient) DeleteIdentityProvider(realm, alias string) error { return keycloakClient.delete(fmt.Sprintf("/realms/%s/identity-provider/instances/%s", realm, alias), nil) } + +func (f *IdentityProviderConfig) UnmarshalJSON(data []byte) error { + return unmarshalExtraConfig(data, reflect.ValueOf(f).Elem(), &f.ExtraConfig) +} + +func (f *IdentityProviderConfig) MarshalJSON() ([]byte, error) { + return marshalExtraConfig(reflect.ValueOf(f).Elem(), f.ExtraConfig) +} diff --git a/keycloak/identity_provider_mapper.go b/keycloak/identity_provider_mapper.go index 37e40205e..ae83057cb 100644 --- a/keycloak/identity_provider_mapper.go +++ b/keycloak/identity_provider_mapper.go @@ -1,12 +1,9 @@ package keycloak import ( - "encoding/json" "fmt" "log" "reflect" - "strconv" - "strings" ) type IdentityProviderMapperConfig struct { @@ -68,55 +65,9 @@ func (keycloakClient *KeycloakClient) DeleteIdentityProviderMapper(realm, alias, } func (f *IdentityProviderMapperConfig) UnmarshalJSON(data []byte) error { - f.ExtraConfig = map[string]interface{}{} - err := json.Unmarshal(data, &f.ExtraConfig) - if err != nil { - return err - } - v := reflect.ValueOf(f).Elem() - for i := 0; i < v.NumField(); i++ { - structField := v.Type().Field(i) - jsonKey := strings.Split(structField.Tag.Get("json"), ",")[0] - if jsonKey != "-" { - value, ok := f.ExtraConfig[jsonKey] - if ok { - field := v.FieldByName(structField.Name) - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - field.SetString(value.(string)) - } else if field.Kind() == reflect.Bool { - boolVal, err := strconv.ParseBool(value.(string)) - if err == nil { - field.Set(reflect.ValueOf(KeycloakBoolQuoted(boolVal))) - } - } - delete(f.ExtraConfig, jsonKey) - } - } - } - } - return nil + return unmarshalExtraConfig(data, reflect.ValueOf(f).Elem(), &f.ExtraConfig) } func (f *IdentityProviderMapperConfig) MarshalJSON() ([]byte, error) { - out := map[string]interface{}{} - - for k, v := range f.ExtraConfig { - out[k] = v - } - v := reflect.ValueOf(f).Elem() - for i := 0; i < v.NumField(); i++ { - jsonKey := strings.Split(v.Type().Field(i).Tag.Get("json"), ",")[0] - if jsonKey != "-" { - field := v.Field(i) - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - out[jsonKey] = field.String() - } else if field.Kind() == reflect.Bool { - out[jsonKey] = KeycloakBoolQuoted(field.Bool()) - } - } - } - } - return json.Marshal(out) + return marshalExtraConfig(reflect.ValueOf(f).Elem(), f.ExtraConfig) } diff --git a/provider/generic_keycloak_identity_provider.go b/provider/generic_keycloak_identity_provider.go index 0bb217b21..fd1b8ed91 100644 --- a/provider/generic_keycloak_identity_provider.go +++ b/provider/generic_keycloak_identity_provider.go @@ -151,19 +151,11 @@ func resourceKeycloakIdentityProvider() *schema.Resource { func getIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, *keycloak.IdentityProviderConfig) { // some identity provider config is shared among all identity providers, so this default config will be used as a base to merge extra config into defaultIdentityProviderConfig := &keycloak.IdentityProviderConfig{ - GuiOrder: data.Get("gui_order").(string), - SyncMode: data.Get("sync_mode").(string), + GuiOrder: data.Get("gui_order").(string), + SyncMode: data.Get("sync_mode").(string), + ExtraConfig: getExtraConfigFromData(data), } - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } - - defaultIdentityProviderConfig.ExtraConfig = extraConfig - return &keycloak.IdentityProvider{ Realm: data.Get("realm").(string), Alias: data.Get("alias").(string), @@ -197,9 +189,9 @@ func setIdentityProviderData(data *schema.ResourceData, identityProvider *keyclo data.Set("post_broker_login_flow_alias", identityProvider.PostBrokerLoginFlowAlias) // identity provider config - data.Set("extra_config", identityProvider.Config.ExtraConfig) data.Set("gui_order", identityProvider.Config.GuiOrder) data.Set("sync_mode", identityProvider.Config.SyncMode) + setExtraConfigData(data, identityProvider.Config.ExtraConfig) } func resourceKeycloakIdentityProviderDelete(data *schema.ResourceData, meta interface{}) error { From 5b79c6c05611f8d3bf8b561a98f97cd4e777a167 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 13:20:16 -0500 Subject: [PATCH 6/9] consume extra config functions for identity provider mappers --- ...neric_keycloak_identity_provider_mapper.go | 5 +++++ ...ibute_importer_identity_provider_mapper.go | 17 ++++++----------- ...ribute_to_role_identity_provider_mapper.go | 17 ++++++----------- ...eycloak_custom_identity_provider_mapper.go | 15 +++++---------- ...oded_attribute_identity_provider_mapper.go | 19 +++++++------------ ...hardcoded_role_identity_provider_mapper.go | 15 ++++----------- ...plate_importer_identity_provider_mapper.go | 15 ++++----------- 7 files changed, 37 insertions(+), 66 deletions(-) diff --git a/provider/generic_keycloak_identity_provider_mapper.go b/provider/generic_keycloak_identity_provider_mapper.go index c1b27b328..9aa1f345f 100644 --- a/provider/generic_keycloak_identity_provider_mapper.go +++ b/provider/generic_keycloak_identity_provider_mapper.go @@ -50,6 +50,9 @@ func getIdentityProviderMapperFromData(data *schema.ResourceData) (*keycloak.Ide Realm: data.Get("realm").(string), Name: data.Get("name").(string), IdentityProviderAlias: data.Get("identity_provider_alias").(string), + Config: &keycloak.IdentityProviderMapperConfig{ + ExtraConfig: getExtraConfigFromData(data), + }, } return rec, nil } @@ -59,6 +62,8 @@ func setIdentityProviderMapperData(data *schema.ResourceData, identityProviderMa data.Set("realm", identityProviderMapper.Realm) data.Set("name", identityProviderMapper.Name) data.Set("identity_provider_alias", identityProviderMapper.IdentityProviderAlias) + setExtraConfigData(data, identityProviderMapper.Config.ExtraConfig) + return nil } diff --git a/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go b/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go index 4633fb622..da221f8af 100644 --- a/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go +++ b/provider/resource_keycloak_attribute_importer_identity_provider_mapper.go @@ -42,22 +42,16 @@ func resourceKeycloakAttributeImporterIdentityProviderMapper() *schema.Resource func getAttributeImporterIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias) if err != nil { return nil, handleNotFoundError(err, data) } + rec.IdentityProviderMapper = fmt.Sprintf("%s-user-attribute-idp-mapper", identityProvider.ProviderId) - rec.Config = &keycloak.IdentityProviderMapperConfig{ - UserAttribute: data.Get("user_attribute").(string), - ExtraConfig: extraConfig, - } + rec.Config.UserAttribute = data.Get("user_attribute").(string) + if identityProvider.ProviderId == "saml" { if attr, ok := data.GetOk("attribute_friendly_name"); ok { rec.Config.AttributeFriendlyName = attr.(string) @@ -79,6 +73,7 @@ func getAttributeImporterIdentityProviderMapperFromData(data *schema.ResourceDat } else { return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_importer_identity_provider_mapper: %s: "%s" identity provider is not supported yet`, data.Get("name").(string), identityProvider.ProviderId) } + return rec, nil } @@ -94,6 +89,6 @@ func setAttributeImporterIdentityProviderMapperData(data *schema.ResourceData, i data.Set("user_attribute", identityProviderMapper.Config.UserAttribute) data.Set("attribute_friendly_name", identityProviderMapper.Config.AttributeFriendlyName) data.Set("claim_name", claimName) - data.Set("extra_config", identityProviderMapper.Config.ExtraConfig) + return nil } diff --git a/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go b/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go index 79d350298..82620b44b 100644 --- a/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go +++ b/provider/resource_keycloak_attribute_to_role_identity_provider_mapper.go @@ -52,22 +52,16 @@ func resourceKeycloakAttributeToRoleIdentityProviderMapper() *schema.Resource { func getAttributeToRoleIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias) if err != nil { return nil, handleNotFoundError(err, data) } + rec.IdentityProviderMapper = fmt.Sprintf("%s-role-idp-mapper", identityProvider.ProviderId) - rec.Config = &keycloak.IdentityProviderMapperConfig{ - Role: data.Get("role").(string), - ExtraConfig: extraConfig, - } + rec.Config.Role = data.Get("role").(string) + if identityProvider.ProviderId == "saml" { if attr, ok := data.GetOk("attribute_friendly_name"); ok { rec.Config.AttributeFriendlyName = attr.(string) @@ -92,6 +86,7 @@ func getAttributeToRoleIdentityProviderMapperFromData(data *schema.ResourceData, } else { return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_to_role_identity_provider_mapper: %s: "%s" identity provider is not supported yet`, data.Get("name").(string), identityProvider.ProviderId) } + return rec, nil } @@ -103,6 +98,6 @@ func setAttributeToRoleIdentityProviderMapperData(data *schema.ResourceData, ide data.Set("claim_name", identityProviderMapper.Config.Claim) data.Set("claim_value", identityProviderMapper.Config.ClaimValue) data.Set("attribute_friendly_name", identityProviderMapper.Config.AttributeFriendlyName) - data.Set("extra_config", identityProviderMapper.Config.ExtraConfig) + return nil } diff --git a/provider/resource_keycloak_custom_identity_provider_mapper.go b/provider/resource_keycloak_custom_identity_provider_mapper.go index ea74fd725..6acf0d3a6 100644 --- a/provider/resource_keycloak_custom_identity_provider_mapper.go +++ b/provider/resource_keycloak_custom_identity_provider_mapper.go @@ -26,32 +26,27 @@ func resourceKeycloakCustomIdentityProviderMapper() *schema.Resource { func getCustomIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias) if err != nil { return nil, handleNotFoundError(err, data) } + identityProviderMapper := data.Get("identity_provider_mapper").(string) if strings.Contains(identityProviderMapper, "%s") { rec.IdentityProviderMapper = fmt.Sprintf(identityProviderMapper, identityProvider.ProviderId) } else { rec.IdentityProviderMapper = identityProviderMapper } - rec.Config = &keycloak.IdentityProviderMapperConfig{ - ExtraConfig: extraConfig, - } + return rec, nil } func setCustomIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { setIdentityProviderMapperData(data, identityProviderMapper) + setExtraConfigData(data, identityProviderMapper.Config.ExtraConfig) data.Set("identity_provider_mapper", identityProviderMapper.IdentityProviderMapper) - data.Set("extra_config", identityProviderMapper.Config.ExtraConfig) + return nil } diff --git a/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go b/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go index 1effb1269..383e7ab7c 100644 --- a/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go +++ b/provider/resource_keycloak_hardcoded_attribute_identity_provider_mapper.go @@ -36,18 +36,11 @@ func resourceKeycloakHardcodedAttributeIdentityProviderMapper() *schema.Resource func getHardcodedAttributeIdentityProviderMapperFromData(data *schema.ResourceData, _ interface{}) (*keycloak.IdentityProviderMapper, error) { rec, _ := getIdentityProviderMapperFromData(data) - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } + rec.IdentityProviderMapper = getHardcodedAttributeIdentityProviderMapperType(data.Get("user_session").(bool)) - rec.Config = &keycloak.IdentityProviderMapperConfig{ - HardcodedAttribute: data.Get("attribute_name").(string), - AttributeValue: data.Get("attribute_value").(string), - ExtraConfig: extraConfig, - } + rec.Config.HardcodedAttribute = data.Get("attribute_name").(string) + rec.Config.AttributeValue = data.Get("attribute_value").(string) + return rec, nil } @@ -55,12 +48,14 @@ func setHardcodedAttributeIdentityProviderMapperData(data *schema.ResourceData, setIdentityProviderMapperData(data, identityProviderMapper) data.Set("attribute_name", identityProviderMapper.Config.HardcodedAttribute) data.Set("attribute_value", identityProviderMapper.Config.AttributeValue) - data.Set("extra_config", identityProviderMapper.Config.ExtraConfig) + mapperType, err := getUserSessionFromHardcodedAttributeIdentityProviderMapperType(identityProviderMapper.IdentityProviderMapper) if err != nil { return err } + data.Set("user_session", mapperType) + return nil } diff --git a/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go b/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go index c38c32822..4bdef9eaa 100644 --- a/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go +++ b/provider/resource_keycloak_hardcoded_role_identity_provider_mapper.go @@ -23,23 +23,16 @@ func resourceKeycloakHardcodedRoleIdentityProviderMapper() *schema.Resource { func getHardcodedRoleIdentityProviderMapperFromData(data *schema.ResourceData, _ interface{}) (*keycloak.IdentityProviderMapper, error) { rec, _ := getIdentityProviderMapperFromData(data) - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } + rec.IdentityProviderMapper = "oidc-hardcoded-role-idp-mapper" - rec.Config = &keycloak.IdentityProviderMapperConfig{ - Role: data.Get("role").(string), - ExtraConfig: extraConfig, - } + rec.Config.Role = data.Get("role").(string) + return rec, nil } func setHardcodedRoleIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { setIdentityProviderMapperData(data, identityProviderMapper) data.Set("role", identityProviderMapper.Config.Role) - data.Set("extra_config", identityProviderMapper.Config.ExtraConfig) + return nil } diff --git a/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go b/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go index 455933358..48a5309ba 100644 --- a/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go +++ b/provider/resource_keycloak_user_template_importer_identity_provider_mapper.go @@ -25,13 +25,8 @@ func resourceKeycloakUserTemplateImporterIdentityProviderMapper() *schema.Resour func getUserTemplateImporterIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias) if err != nil { return nil, handleNotFoundError(err, data) @@ -43,16 +38,14 @@ func getUserTemplateImporterIdentityProviderMapperFromData(data *schema.Resource rec.IdentityProviderMapper = fmt.Sprintf("%s-username-idp-mapper", identityProvider.ProviderId) } - rec.Config = &keycloak.IdentityProviderMapperConfig{ - Template: data.Get("template").(string), - ExtraConfig: extraConfig, - } + rec.Config.Template = data.Get("template").(string) + return rec, nil } func setUserTemplateImporterIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { setIdentityProviderMapperData(data, identityProviderMapper) data.Set("template", identityProviderMapper.Config.Template) - data.Set("extra_config", identityProviderMapper.Config.ExtraConfig) + return nil } From 8b32ca43eb52362e3c6c04c8ad3ce4a16ae5f5d6 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 15:02:51 -0500 Subject: [PATCH 7/9] fix tests for versions under 13 --- provider/resource_keycloak_openid_client_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go index ec3d19b51..25ee059c7 100644 --- a/provider/resource_keycloak_openid_client_test.go +++ b/provider/resource_keycloak_openid_client_test.go @@ -847,7 +847,16 @@ func testAccCheckKeycloakOpenidClientExtraConfigMissing(resourceName string, key return err } - if _, ok := client.Attributes.ExtraConfig[key]; ok { + if val, ok := client.Attributes.ExtraConfig[key]; ok { + // keycloak 13+ will remove attributes if set to empty string. on older versions, we'll just check if this value is empty + if versionOk, _ := keycloakClient.VersionIsGreaterThanOrEqualTo(keycloak.Version_13); !versionOk { + if val != "" { + return fmt.Errorf("expected openid client to have empty attribute %v", key) + } + + return nil + } + return fmt.Errorf("expected openid client to not have attribute %v", key) } From d63735b5cea3a877de64a45124e8cbd8b8bb8c1b Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 16:10:12 -0500 Subject: [PATCH 8/9] extract validation func to helpers --- provider/extra_config.go | 35 +++++++++++++++++++ .../generic_keycloak_identity_provider.go | 34 ++---------------- provider/resource_keycloak_openid_client.go | 34 ++---------------- 3 files changed, 41 insertions(+), 62 deletions(-) diff --git a/provider/extra_config.go b/provider/extra_config.go index a88fb5341..f20e5d893 100644 --- a/provider/extra_config.go +++ b/provider/extra_config.go @@ -1,7 +1,12 @@ package provider import ( + "fmt" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "reflect" + "strings" ) func getExtraConfigFromData(data *schema.ResourceData) map[string]interface{} { @@ -43,3 +48,33 @@ func setExtraConfigData(data *schema.ResourceData, extraConfig map[string]interf data.Set("extra_config", c) } + +// validateExtraConfig takes a reflect value type to check its JSON schema in order to validate that extra_config +// doesn't contain any attributes that could have been specified within the official schema +func validateExtraConfig(reflectValue reflect.Value) func(interface{}, cty.Path) diag.Diagnostics { + return func(v interface{}, path cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + + extraConfig := v.(map[string]interface{}) + + for i := 0; i < reflectValue.NumField(); i++ { + field := reflectValue.Field(i) + jsonKey := strings.Split(reflectValue.Type().Field(i).Tag.Get("json"), ",")[0] + + if jsonKey != "-" && field.CanSet() { + if _, ok := extraConfig[jsonKey]; ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Invalid extra_config key", + Detail: fmt.Sprintf(`extra_config key "%s" is not allowed, as it conflicts with a top-level schema attribute`, jsonKey), + AttributePath: append(path, cty.IndexStep{ + Key: cty.StringVal(jsonKey), + }), + }) + } + } + } + + return diags + } +} diff --git a/provider/generic_keycloak_identity_provider.go b/provider/generic_keycloak_identity_provider.go index fd1b8ed91..424c26212 100644 --- a/provider/generic_keycloak_identity_provider.go +++ b/provider/generic_keycloak_identity_provider.go @@ -2,8 +2,6 @@ package provider import ( "fmt" - "github.com/hashicorp/go-cty/cty" - "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" @@ -101,35 +99,9 @@ func resourceKeycloakIdentityProvider() *schema.Resource { }, // all schema values below this point will be configuration values that are shared among all identity providers "extra_config": { - Type: schema.TypeMap, - Optional: true, - // you aren't allowed to specify any keys in extra_config that could be defined as top level attributes - ValidateDiagFunc: func(v interface{}, path cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - - extraConfig := v.(map[string]interface{}) - value := reflect.ValueOf(&keycloak.IdentityProviderConfig{}).Elem() - - for i := 0; i < value.NumField(); i++ { - field := value.Field(i) - jsonKey := strings.Split(value.Type().Field(i).Tag.Get("json"), ",")[0] - - if jsonKey != "-" && field.CanSet() { - if _, ok := extraConfig[jsonKey]; ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Invalid extra_config key", - Detail: fmt.Sprintf(`extra_config key "%s" is not allowed, as it conflicts with a top-level schema attribute`, jsonKey), - AttributePath: append(path, cty.IndexStep{ - Key: cty.StringVal(jsonKey), - }), - }) - } - } - } - - return diags - }, + Type: schema.TypeMap, + Optional: true, + ValidateDiagFunc: validateExtraConfig(reflect.ValueOf(&keycloak.IdentityProviderConfig{}).Elem()), }, "gui_order": { Type: schema.TypeString, diff --git a/provider/resource_keycloak_openid_client.go b/provider/resource_keycloak_openid_client.go index 5ff23046f..3028b7026 100644 --- a/provider/resource_keycloak_openid_client.go +++ b/provider/resource_keycloak_openid_client.go @@ -7,8 +7,6 @@ import ( "reflect" "strings" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -228,35 +226,9 @@ func resourceKeycloakOpenidClient() *schema.Resource { Optional: true, }, "extra_config": { - Type: schema.TypeMap, - Optional: true, - // you aren't allowed to specify any keys in extra_config that could be defined as top level attributes - ValidateDiagFunc: func(v interface{}, path cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - - extraConfig := v.(map[string]interface{}) - value := reflect.ValueOf(&keycloak.OpenidClientAttributes{}).Elem() - - for i := 0; i < value.NumField(); i++ { - field := value.Field(i) - jsonKey := strings.Split(value.Type().Field(i).Tag.Get("json"), ",")[0] - - if jsonKey != "-" && field.CanSet() { - if _, ok := extraConfig[jsonKey]; ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Invalid extra_config key", - Detail: fmt.Sprintf(`extra_config key "%s" is not allowed, as it conflicts with a top-level schema attribute`, jsonKey), - AttributePath: append(path, cty.IndexStep{ - Key: cty.StringVal(jsonKey), - }), - }) - } - } - } - - return diags - }, + Type: schema.TypeMap, + Optional: true, + ValidateDiagFunc: validateExtraConfig(reflect.ValueOf(&keycloak.OpenidClientAttributes{}).Elem()), }, }, CustomizeDiff: customdiff.ComputedIf("service_account_user_id", func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { From e7754960de0ff3f5289bf9112d5b33406c22f24f Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Fri, 3 Sep 2021 16:12:30 -0500 Subject: [PATCH 9/9] change return type --- provider/extra_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/extra_config.go b/provider/extra_config.go index f20e5d893..46d461006 100644 --- a/provider/extra_config.go +++ b/provider/extra_config.go @@ -51,7 +51,7 @@ func setExtraConfigData(data *schema.ResourceData, extraConfig map[string]interf // validateExtraConfig takes a reflect value type to check its JSON schema in order to validate that extra_config // doesn't contain any attributes that could have been specified within the official schema -func validateExtraConfig(reflectValue reflect.Value) func(interface{}, cty.Path) diag.Diagnostics { +func validateExtraConfig(reflectValue reflect.Value) schema.SchemaValidateDiagFunc { return func(v interface{}, path cty.Path) diag.Diagnostics { var diags diag.Diagnostics