From 20e5a90400aec7edb85be3fe0a3edb286a209208 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Thu, 29 Apr 2021 19:05:58 -0500 Subject: [PATCH 1/4] fix extra_config usage for identity providers --- example/main.tf | 131 ++++++++++-------- go.mod | 2 + go.sum | 4 + .../generic_keycloak_identity_provider.go | 105 ++++++++++++-- ..._keycloak_oidc_google_identity_provider.go | 24 ++-- ...loak_oidc_google_identity_provider_test.go | 37 ++++- ...esource_keycloak_oidc_identity_provider.go | 24 ++-- ...ce_keycloak_oidc_identity_provider_test.go | 38 ++++- ...esource_keycloak_saml_identity_provider.go | 41 ++---- ...ce_keycloak_saml_identity_provider_test.go | 90 +++++++++++- 10 files changed, 349 insertions(+), 147 deletions(-) diff --git a/example/main.tf b/example/main.tf index c2eab8220..f74468d18 100644 --- a/example/main.tf +++ b/example/main.tf @@ -8,9 +8,9 @@ terraform { } provider "keycloak" { - client_id = "terraform" - client_secret = "884e0f95-0f42-4a63-9b1f-94274655669e" - url = "http://localhost:8080" + client_id = "terraform" + client_secret = "884e0f95-0f42-4a63-9b1f-94274655669e" + url = "http://localhost:8080" additional_headers = { foo = "bar" } @@ -76,20 +76,24 @@ resource "keycloak_realm" "test" { ssl_required = "external" password_policy = "upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername" - attributes = { + attributes = { mycustomAttribute = "myCustomValue" } web_authn_policy { relying_party_entity_name = "Example" - relying_party_id = "keycloak.example.com" - signature_algorithms = ["ES256", "RS256"] + relying_party_id = "keycloak.example.com" + signature_algorithms = [ + "ES256", + "RS256"] } web_authn_passwordless_policy { relying_party_entity_name = "Example" - relying_party_id = "keycloak.example.com" - signature_algorithms = ["ES256", "RS256"] + relying_party_id = "keycloak.example.com" + signature_algorithms = [ + "ES256", + "RS256"] } } @@ -111,10 +115,10 @@ resource "keycloak_required_action" "custom-configured_totp" { } resource "keycloak_required_action" "required_action" { - realm_id = keycloak_realm.test.realm - alias = "webauthn-register" - enabled = true - name = "Webauthn Register" + realm_id = keycloak_realm.test.realm + alias = "webauthn-register" + enabled = true + name = "Webauthn Register" } resource "keycloak_group" "foo" { @@ -182,7 +186,8 @@ resource "keycloak_group" "baz" { resource "keycloak_default_groups" "default" { realm_id = keycloak_realm.test.id - group_ids = [keycloak_group.baz.id] + group_ids = [ + keycloak_group.baz.id] } resource "keycloak_openid_client" "test_client" { @@ -274,10 +279,10 @@ resource "keycloak_ldap_user_federation" "openldap" { read_timeout = "10s" kerberos { - server_principal = "HTTP/keycloak.local@FOO.LOCAL" + server_principal = "HTTP/keycloak.local@FOO.LOCAL" use_kerberos_for_password_authentication = false - key_tab = "/etc/keycloak.keytab" - kerberos_realm = "FOO.LOCAL" + key_tab = "/etc/keycloak.keytab" + kerberos_realm = "FOO.LOCAL" } cache { @@ -450,15 +455,15 @@ resource "keycloak_openid_user_client_role_protocol_mapper" "user_client_role_cl realm_id = keycloak_realm.test.id client_id = keycloak_openid_client.test_client.id - claim_name = "foo" + claim_name = "foo" multivalued = false - client_id_for_role_mappings = keycloak_openid_client.bearer_only_client.client_id - client_role_prefix = "prefixValue" + client_id_for_role_mappings = keycloak_openid_client.bearer_only_client.client_id + client_role_prefix = "prefixValue" add_to_id_token = true add_to_access_token = false - add_to_userinfo = false + add_to_userinfo = false } resource "keycloak_openid_user_client_role_protocol_mapper" "user_client_role_client_scope" { @@ -469,35 +474,35 @@ resource "keycloak_openid_user_client_role_protocol_mapper" "user_client_role_cl claim_name = "foo" multivalued = false - client_id_for_role_mappings = keycloak_openid_client.bearer_only_client.client_id - client_role_prefix = "prefixValue" + client_id_for_role_mappings = keycloak_openid_client.bearer_only_client.client_id + client_role_prefix = "prefixValue" add_to_id_token = true add_to_access_token = false - add_to_userinfo = false + add_to_userinfo = false } resource "keycloak_openid_user_session_note_protocol_mapper" "user_session_note_client" { - name = "tf-test-open-id-user-session-note-protocol-mapper-client" - realm_id = keycloak_realm.test.id - client_id = keycloak_openid_client.test_client.id + name = "tf-test-open-id-user-session-note-protocol-mapper-client" + realm_id = keycloak_realm.test.id + client_id = keycloak_openid_client.test_client.id - claim_name = "foo" - claim_value_type = "String" - session_note = "bar" + claim_name = "foo" + claim_value_type = "String" + session_note = "bar" add_to_id_token = true add_to_access_token = false } resource "keycloak_openid_user_session_note_protocol_mapper" "user_session_note_client_scope" { - name = "tf-test-open-id-user-session-note-protocol-mapper-client-scope" - realm_id = keycloak_realm.test.id - client_scope_id = keycloak_openid_client_scope.test_default_client_scope.id + name = "tf-test-open-id-user-session-note-protocol-mapper-client-scope" + realm_id = keycloak_realm.test.id + client_scope_id = keycloak_openid_client_scope.test_default_client_scope.id - claim_name = "foo2" - claim_value_type = "String" - session_note = "bar2" + claim_name = "foo2" + claim_value_type = "String" + session_note = "bar2" add_to_id_token = true add_to_access_token = false @@ -586,6 +591,8 @@ resource keycloak_oidc_identity_provider oidc { client_id = "example_id" client_secret = "example_token" default_scopes = "openid random profile" + sync_mode = "FORCE" + gui_order = 1 } resource keycloak_oidc_google_identity_provider google { @@ -596,6 +603,8 @@ resource keycloak_oidc_google_identity_provider google { request_refresh_token = true default_scopes = "openid random profile" accepts_prompt_none_forward_from_client = false + sync_mode = "FORCE" + gui_order = 2 } //This example does not work in keycloak 10, because the interfaces that our customIdp implements, have changed in the keycloak latest version. @@ -684,6 +693,8 @@ resource keycloak_saml_identity_provider saml { alias = "saml" entity_id = "https://example.com/entity_id" single_sign_on_service_url = "https://example.com/auth" + sync_mode = "FORCE" + gui_order = 3 } resource keycloak_attribute_importer_identity_provider_mapper saml { @@ -852,61 +863,61 @@ resource "keycloak_openid_client_service_account_role" "read_token" { } resource "keycloak_authentication_flow" "browser-copy-flow" { - alias = "browserCopyFlow" - realm_id = keycloak_realm.test.id + alias = "browserCopyFlow" + realm_id = keycloak_realm.test.id description = "browser based authentication" } resource "keycloak_authentication_execution" "browser-copy-cookie" { - realm_id = keycloak_realm.test.id + realm_id = keycloak_realm.test.id parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias - authenticator = "auth-cookie" - requirement = "ALTERNATIVE" - depends_on = [ + authenticator = "auth-cookie" + requirement = "ALTERNATIVE" + depends_on = [ keycloak_authentication_execution.browser-copy-kerberos ] } resource "keycloak_authentication_execution" "browser-copy-kerberos" { - realm_id = keycloak_realm.test.id + realm_id = keycloak_realm.test.id parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias - authenticator = "auth-spnego" - requirement = "DISABLED" + authenticator = "auth-spnego" + requirement = "DISABLED" } resource "keycloak_authentication_execution" "browser-copy-idp-redirect" { - realm_id = keycloak_realm.test.id + realm_id = keycloak_realm.test.id parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias - authenticator = "identity-provider-redirector" - requirement = "ALTERNATIVE" - depends_on = [ + authenticator = "identity-provider-redirector" + requirement = "ALTERNATIVE" + depends_on = [ keycloak_authentication_execution.browser-copy-cookie ] } resource "keycloak_authentication_subflow" "browser-copy-flow-forms" { - realm_id = keycloak_realm.test.id + realm_id = keycloak_realm.test.id parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias - alias = "browser-copy-flow-forms" - requirement = "ALTERNATIVE" - depends_on = [ + alias = "browser-copy-flow-forms" + requirement = "ALTERNATIVE" + depends_on = [ keycloak_authentication_execution.browser-copy-idp-redirect ] } resource "keycloak_authentication_execution" "browser-copy-auth-username-password-form" { - realm_id = keycloak_realm.test.id + realm_id = keycloak_realm.test.id parent_flow_alias = keycloak_authentication_subflow.browser-copy-flow-forms.alias - authenticator = "auth-username-password-form" - requirement = "REQUIRED" + authenticator = "auth-username-password-form" + requirement = "REQUIRED" } resource "keycloak_authentication_execution" "browser-copy-otp" { - realm_id = keycloak_realm.test.id + realm_id = keycloak_realm.test.id parent_flow_alias = keycloak_authentication_subflow.browser-copy-flow-forms.alias - authenticator = "auth-otp-form" - requirement = "REQUIRED" - depends_on = [ + authenticator = "auth-otp-form" + requirement = "REQUIRED" + depends_on = [ keycloak_authentication_execution.browser-copy-auth-username-password-form ] } @@ -915,7 +926,7 @@ resource "keycloak_authentication_execution_config" "config" { realm_id = keycloak_realm.test.id execution_id = keycloak_authentication_execution.browser-copy-idp-redirect.id alias = "idp-XXX-config" - config = { + config = { defaultProvider = "idp-XXX" } } diff --git a/go.mod b/go.mod index 2f70de9da..c6a921f3d 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,10 @@ module github.com/mrparkers/terraform-provider-keycloak require ( github.com/hashicorp/errwrap v1.0.0 + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-version v1.2.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.2-0.20200817173939-b72757e734f6 + github.com/imdario/mergo v0.3.12 golang.org/x/net v0.0.0-20200707034311-ab3426394381 ) diff --git a/go.sum b/go.sum index 3074b7249..e5968627a 100644 --- a/go.sum +++ b/go.sum @@ -187,6 +187,8 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= @@ -531,6 +533,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/provider/generic_keycloak_identity_provider.go b/provider/generic_keycloak_identity_provider.go index 8b4949bb3..0bb217b21 100644 --- a/provider/generic_keycloak_identity_provider.go +++ b/provider/generic_keycloak_identity_provider.go @@ -2,11 +2,21 @@ 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" + "reflect" "strings" ) +var syncModes = []string{ + "IMPORT", + "FORCE", + "LEGACY", +} + type identityProviderDataGetterFunc func(data *schema.ResourceData) (*keycloak.IdentityProvider, error) type identityProviderDataSetterFunc func(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error @@ -89,12 +99,72 @@ func resourceKeycloakIdentityProvider() *schema.Resource { Default: "", Description: "Alias of authentication flow, which is triggered after each login with this identity provider. Useful if you want additional verification of each user authenticated with this identity provider (for example OTP). Leave this empty if you don't want any additional authenticators to be triggered after login with this identity provider. Also note, that authenticator implementations must assume that user is already set in ClientSession as identity provider already set it.", }, + // 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 + }, + }, + "gui_order": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "GUI Order", + }, + "sync_mode": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice(syncModes, false), + Description: "Sync Mode", + }, }, } } -func getIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { - rec := &keycloak.IdentityProvider{ +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), + } + + 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), DisplayName: data.Get("display_name").(string), @@ -107,12 +177,12 @@ func getIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityP FirstBrokerLoginFlowAlias: data.Get("first_broker_login_flow_alias").(string), PostBrokerLoginFlowAlias: data.Get("post_broker_login_flow_alias").(string), InternalId: data.Get("internal_id").(string), - } - return rec, nil + }, defaultIdentityProviderConfig } -func setIdentityProviderData(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error { +func setIdentityProviderData(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) { data.SetId(identityProvider.Alias) + data.Set("internal_id", identityProvider.InternalId) data.Set("realm", identityProvider.Realm) data.Set("alias", identityProvider.Alias) @@ -125,7 +195,11 @@ func setIdentityProviderData(data *schema.ResourceData, identityProvider *keyclo data.Set("trust_email", identityProvider.TrustEmail) data.Set("first_broker_login_flow_alias", identityProvider.FirstBrokerLoginFlowAlias) data.Set("post_broker_login_flow_alias", identityProvider.PostBrokerLoginFlowAlias) - return nil + + // identity provider config + data.Set("extra_config", identityProvider.Config.ExtraConfig) + data.Set("gui_order", identityProvider.Config.GuiOrder) + data.Set("sync_mode", identityProvider.Config.SyncMode) } func resourceKeycloakIdentityProviderDelete(data *schema.ResourceData, meta interface{}) error { @@ -155,6 +229,10 @@ func resourceKeycloakIdentityProviderCreate(getIdentityProviderFromData identity return func(data *schema.ResourceData, meta interface{}) error { keycloakClient := meta.(*keycloak.KeycloakClient) identityProvider, err := getIdentityProviderFromData(data) + if err != nil { + return err + } + if err = keycloakClient.NewIdentityProvider(identityProvider); err != nil { return err } @@ -174,10 +252,8 @@ func resourceKeycloakIdentityProviderRead(setDataFromIdentityProvider identityPr if err != nil { return handleNotFoundError(err, data) } - if err = setDataFromIdentityProvider(data, identityProvider); err != nil { - return err - } - return nil + + return setDataFromIdentityProvider(data, identityProvider) } } @@ -185,12 +261,15 @@ func resourceKeycloakIdentityProviderUpdate(getIdentityProviderFromData identity return func(data *schema.ResourceData, meta interface{}) error { keycloakClient := meta.(*keycloak.KeycloakClient) identityProvider, err := getIdentityProviderFromData(data) - if err = keycloakClient.UpdateIdentityProvider(identityProvider); err != nil { + if err != nil { return err } - if err = setDataFromIdentityProvider(data, identityProvider); err != nil { + + err = keycloakClient.UpdateIdentityProvider(identityProvider) + if err != nil { return err } - return nil + + return setDataFromIdentityProvider(data, identityProvider) } } diff --git a/provider/resource_keycloak_oidc_google_identity_provider.go b/provider/resource_keycloak_oidc_google_identity_provider.go index 20766baab..c78387ad1 100644 --- a/provider/resource_keycloak_oidc_google_identity_provider.go +++ b/provider/resource_keycloak_oidc_google_identity_provider.go @@ -2,6 +2,7 @@ package provider import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/imdario/mergo" "github.com/mrparkers/terraform-provider-keycloak/keycloak" ) @@ -75,10 +76,6 @@ func resourceKeycloakOidcGoogleIdentityProvider() *schema.Resource { Default: false, Description: "Hide On Login Page.", }, - "extra_config": { - Type: schema.TypeMap, - Optional: true, - }, } oidcResource := resourceKeycloakIdentityProvider() oidcResource.Schema = mergeSchemas(oidcResource.Schema, oidcGoogleSchema) @@ -89,18 +86,11 @@ func resourceKeycloakOidcGoogleIdentityProvider() *schema.Resource { } func getOidcGoogleIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { - rec, _ := getIdentityProviderFromData(data) + rec, defaultConfig := getIdentityProviderFromData(data) rec.ProviderId = data.Get("provider_id").(string) rec.Alias = "google" - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } - - rec.Config = &keycloak.IdentityProviderConfig{ + googleOidcIdentityProviderConfig := &keycloak.IdentityProviderConfig{ ClientId: data.Get("client_id").(string), ClientSecret: data.Get("client_secret").(string), HideOnLoginPage: keycloak.KeycloakBoolQuoted(data.Get("hide_on_login_page").(bool)), @@ -109,11 +99,16 @@ func getOidcGoogleIdentityProviderFromData(data *schema.ResourceData) (*keycloak OfflineAccess: keycloak.KeycloakBoolQuoted(data.Get("request_refresh_token").(bool)), DefaultScope: data.Get("default_scopes").(string), AcceptsPromptNoneForwFrmClt: keycloak.KeycloakBoolQuoted(data.Get("accepts_prompt_none_forward_from_client").(bool)), - ExtraConfig: extraConfig, UseJwksUrl: true, DisableUserInfo: keycloak.KeycloakBoolQuoted(data.Get("disable_user_info").(bool)), } + if err := mergo.Merge(googleOidcIdentityProviderConfig, defaultConfig); err != nil { + return nil, err + } + + rec.Config = googleOidcIdentityProviderConfig + return rec, nil } @@ -127,7 +122,6 @@ func setOidcGoogleIdentityProviderData(data *schema.ResourceData, identityProvid data.Set("request_refresh_token", identityProvider.Config.OfflineAccess) data.Set("default_scopes", identityProvider.Config.DefaultScope) data.Set("accepts_prompt_none_forward_from_client", identityProvider.Config.AcceptsPromptNoneForwFrmClt) - data.Set("extra_config", identityProvider.Config.ExtraConfig) data.Set("disable_user_info", identityProvider.Config.DisableUserInfo) return nil } diff --git a/provider/resource_keycloak_oidc_google_identity_provider_test.go b/provider/resource_keycloak_oidc_google_identity_provider_test.go index 1b7e48706..caf60784d 100644 --- a/provider/resource_keycloak_oidc_google_identity_provider_test.go +++ b/provider/resource_keycloak_oidc_google_identity_provider_test.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "regexp" + "strconv" "testing" ) @@ -23,7 +25,7 @@ func TestAccKeycloakOidcGoogleIdentityProvider_basic(t *testing.T) { }) } -func TestAccKeycloakOidcGoogleIdentityProvider_customConfig(t *testing.T) { +func TestAccKeycloakOidcGoogleIdentityProvider_extraConfig(t *testing.T) { customConfigValue := acctest.RandomWithPrefix("tf-acc") resource.Test(t, resource.TestCase{ @@ -32,7 +34,7 @@ func TestAccKeycloakOidcGoogleIdentityProvider_customConfig(t *testing.T) { CheckDestroy: testAccCheckKeycloakOidcGoogleIdentityProviderDestroy(), Steps: []resource.TestStep{ { - Config: testKeycloakOidcGoogleIdentityProvider_customConfig(customConfigValue), + Config: testKeycloakOidcGoogleIdentityProvider_customConfig("dummyConfig", customConfigValue), Check: resource.ComposeTestCheckFunc( testAccCheckKeycloakOidcGoogleIdentityProviderExists("keycloak_oidc_google_identity_provider.google_custom"), testAccCheckKeycloakOidcGoogleIdentityProviderHasCustomConfigValue("keycloak_oidc_google_identity_provider.google_custom", customConfigValue), @@ -42,6 +44,23 @@ func TestAccKeycloakOidcGoogleIdentityProvider_customConfig(t *testing.T) { }) } +// ensure that extra_config keys which are covered by top-level attributes are not allowed +func TestAccKeycloakOidcGoogleIdentityProvider_extraConfigInvalid(t *testing.T) { + customConfigValue := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOidcGoogleIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOidcGoogleIdentityProvider_customConfig("syncMode", customConfigValue), + ExpectError: regexp.MustCompile("extra_config key \"syncMode\" is not allowed"), + }, + }, + }) +} + func TestAccKeycloakOidcGoogleIdentityProvider_createAfterManualDestroy(t *testing.T) { var idp = &keycloak.IdentityProvider{} @@ -79,6 +98,8 @@ func TestAccKeycloakOidcGoogleIdentityProvider_basicUpdateAll(t *testing.T) { AcceptsPromptNoneForwFrmClt: false, ClientId: acctest.RandString(10), ClientSecret: acctest.RandString(10), + GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)), + SyncMode: randomStringInSlice(syncModes), }, } @@ -90,6 +111,8 @@ func TestAccKeycloakOidcGoogleIdentityProvider_basicUpdateAll(t *testing.T) { AcceptsPromptNoneForwFrmClt: false, ClientId: acctest.RandString(10), ClientSecret: acctest.RandString(10), + GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)), + SyncMode: randomStringInSlice(syncModes), }, } @@ -201,7 +224,7 @@ resource "keycloak_oidc_google_identity_provider" "google" { `, testAccRealm.Realm) } -func testKeycloakOidcGoogleIdentityProvider_customConfig(customConfigValue string) string { +func testKeycloakOidcGoogleIdentityProvider_customConfig(configKey, configValue string) string { return fmt.Sprintf(` data "keycloak_realm" "realm" { realm = "%s" @@ -213,10 +236,10 @@ resource "keycloak_oidc_google_identity_provider" "google_custom" { client_id = "example_id" client_secret = "example_token" extra_config = { - dummyConfig = "%s" + %s = "%s" } } - `, testAccRealm.Realm, customConfigValue) + `, testAccRealm.Realm, configKey, configValue) } func testKeycloakOidcGoogleIdentityProvider_basicFromInterface(idp *keycloak.IdentityProvider) string { @@ -232,6 +255,8 @@ resource "keycloak_oidc_google_identity_provider" "google" { accepts_prompt_none_forward_from_client = %t client_id = "%s" client_secret = "%s" + gui_order = %s + sync_mode = "%s" } - `, testAccRealm.Realm, idp.Enabled, idp.Config.HostedDomain, idp.Config.AcceptsPromptNoneForwFrmClt, idp.Config.ClientId, idp.Config.ClientSecret) + `, testAccRealm.Realm, idp.Enabled, idp.Config.HostedDomain, idp.Config.AcceptsPromptNoneForwFrmClt, idp.Config.ClientId, idp.Config.ClientSecret, idp.Config.GuiOrder, idp.Config.SyncMode) } diff --git a/provider/resource_keycloak_oidc_identity_provider.go b/provider/resource_keycloak_oidc_identity_provider.go index aed179567..ff900cf9a 100644 --- a/provider/resource_keycloak_oidc_identity_provider.go +++ b/provider/resource_keycloak_oidc_identity_provider.go @@ -2,6 +2,7 @@ package provider import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/imdario/mergo" "github.com/mrparkers/terraform-provider-keycloak/keycloak" ) @@ -96,10 +97,6 @@ func resourceKeycloakOidcIdentityProvider() *schema.Resource { Default: false, Description: "Disable usage of User Info service to obtain additional user information? Default is to use this OIDC service.", }, - "extra_config": { - Type: schema.TypeMap, - Optional: true, - }, } oidcResource := resourceKeycloakIdentityProvider() oidcResource.Schema = mergeSchemas(oidcResource.Schema, oidcSchema) @@ -110,18 +107,11 @@ func resourceKeycloakOidcIdentityProvider() *schema.Resource { } func getOidcIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { - rec, _ := getIdentityProviderFromData(data) + rec, defaultConfig := getIdentityProviderFromData(data) rec.ProviderId = data.Get("provider_id").(string) _, useJwksUrl := data.GetOk("jwks_url") - extraConfig := map[string]interface{}{} - if v, ok := data.GetOk("extra_config"); ok { - for key, value := range v.(map[string]interface{}) { - extraConfig[key] = value - } - } - - rec.Config = &keycloak.IdentityProviderConfig{ + oidcIdentityProviderConfig := &keycloak.IdentityProviderConfig{ BackchannelSupported: keycloak.KeycloakBoolQuoted(data.Get("backchannel_supported").(bool)), ValidateSignature: keycloak.KeycloakBoolQuoted(data.Get("validate_signature").(bool)), AuthorizationUrl: data.Get("authorization_url").(string), @@ -134,13 +124,18 @@ func getOidcIdentityProviderFromData(data *schema.ResourceData) (*keycloak.Ident LoginHint: data.Get("login_hint").(string), JwksUrl: data.Get("jwks_url").(string), UserInfoUrl: data.Get("user_info_url").(string), - ExtraConfig: extraConfig, UseJwksUrl: keycloak.KeycloakBoolQuoted(useJwksUrl), DisableUserInfo: keycloak.KeycloakBoolQuoted(data.Get("disable_user_info").(bool)), DefaultScope: data.Get("default_scopes").(string), AcceptsPromptNoneForwFrmClt: keycloak.KeycloakBoolQuoted(data.Get("accepts_prompt_none_forward_from_client").(bool)), } + if err := mergo.Merge(oidcIdentityProviderConfig, defaultConfig); err != nil { + return nil, err + } + + rec.Config = oidcIdentityProviderConfig + return rec, nil } @@ -158,6 +153,5 @@ func setOidcIdentityProviderData(data *schema.ResourceData, identityProvider *ke data.Set("token_url", identityProvider.Config.TokenUrl) data.Set("login_hint", identityProvider.Config.LoginHint) data.Set("ui_locales", identityProvider.Config.UILocales) - data.Set("extra_config", identityProvider.Config.ExtraConfig) return nil } diff --git a/provider/resource_keycloak_oidc_identity_provider_test.go b/provider/resource_keycloak_oidc_identity_provider_test.go index 8da04d58d..de4bb435e 100644 --- a/provider/resource_keycloak_oidc_identity_provider_test.go +++ b/provider/resource_keycloak_oidc_identity_provider_test.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "regexp" + "strconv" "testing" ) @@ -25,7 +27,7 @@ func TestAccKeycloakOidcIdentityProvider_basic(t *testing.T) { }) } -func TestAccKeycloakOidcIdentityProvider_extra_config(t *testing.T) { +func TestAccKeycloakOidcIdentityProvider_extraConfig(t *testing.T) { oidcName := acctest.RandomWithPrefix("tf-acc") customConfigValue := acctest.RandomWithPrefix("tf-acc") @@ -35,7 +37,7 @@ func TestAccKeycloakOidcIdentityProvider_extra_config(t *testing.T) { CheckDestroy: testAccCheckKeycloakOidcIdentityProviderDestroy(), Steps: []resource.TestStep{ { - Config: testKeycloakOidcIdentityProvider_extra_config(oidcName, customConfigValue), + Config: testKeycloakOidcIdentityProvider_extra_config(oidcName, "dummyConfig", customConfigValue), Check: resource.ComposeTestCheckFunc( testAccCheckKeycloakOidcIdentityProviderHasCustomConfigValue("keycloak_oidc_identity_provider.oidc", customConfigValue), ), @@ -44,6 +46,24 @@ func TestAccKeycloakOidcIdentityProvider_extra_config(t *testing.T) { }) } +// ensure that extra_config keys which are covered by top-level attributes are not allowed +func TestAccKeycloakOidcIdentityProvider_extraConfigInvalid(t *testing.T) { + oidcName := acctest.RandomWithPrefix("tf-acc") + customConfigValue := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOidcIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOidcIdentityProvider_extra_config(oidcName, "syncMode", customConfigValue), + ExpectError: regexp.MustCompile("extra_config key \"syncMode\" is not allowed"), + }, + }, + }) +} + func TestAccKeycloakOidcIdentityProvider_keyDefaultScopes(t *testing.T) { oidcName := acctest.RandomWithPrefix("tf-acc") @@ -103,6 +123,8 @@ func TestAccKeycloakOidcIdentityProvider_basicUpdateAll(t *testing.T) { TokenUrl: "https://example.com/token", ClientId: acctest.RandString(10), ClientSecret: acctest.RandString(10), + GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)), + SyncMode: randomStringInSlice(syncModes), }, } @@ -115,6 +137,8 @@ func TestAccKeycloakOidcIdentityProvider_basicUpdateAll(t *testing.T) { TokenUrl: "https://example.com/token", ClientId: acctest.RandString(10), ClientSecret: acctest.RandString(10), + GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)), + SyncMode: randomStringInSlice(syncModes), }, } @@ -244,7 +268,7 @@ resource "keycloak_oidc_identity_provider" "oidc" { `, testAccRealm.Realm, oidc) } -func testKeycloakOidcIdentityProvider_extra_config(alias, customConfigValue string) string { +func testKeycloakOidcIdentityProvider_extra_config(alias, configKey, configValue string) string { return fmt.Sprintf(` data "keycloak_realm" "realm" { realm = "%s" @@ -259,10 +283,10 @@ resource "keycloak_oidc_identity_provider" "oidc" { client_id = "example_id" client_secret = "example_token" extra_config = { - dummyConfig = "%s" + %s = "%s" } } - `, testAccRealm.Realm, alias, customConfigValue) + `, testAccRealm.Realm, alias, configKey, configValue) } func testKeycloakOidcIdentityProvider_keyDefaultScopes(alias, value string) string { @@ -298,6 +322,8 @@ resource "keycloak_oidc_identity_provider" "oidc" { token_url = "%s" client_id = "%s" client_secret = "%s" + gui_order = %s + sync_mode = "%s" } - `, testAccRealm.Realm, oidc.Alias, oidc.Enabled, oidc.Config.AuthorizationUrl, oidc.Config.TokenUrl, oidc.Config.ClientId, oidc.Config.ClientSecret) + `, testAccRealm.Realm, oidc.Alias, oidc.Enabled, oidc.Config.AuthorizationUrl, oidc.Config.TokenUrl, oidc.Config.ClientId, oidc.Config.ClientSecret, oidc.Config.GuiOrder, oidc.Config.SyncMode) } diff --git a/provider/resource_keycloak_saml_identity_provider.go b/provider/resource_keycloak_saml_identity_provider.go index c866bc008..7ee24ef50 100644 --- a/provider/resource_keycloak_saml_identity_provider.go +++ b/provider/resource_keycloak_saml_identity_provider.go @@ -3,6 +3,7 @@ package provider import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/imdario/mergo" "github.com/mrparkers/terraform-provider-keycloak/keycloak" ) @@ -34,12 +35,6 @@ var principalTypes = []string{ "FRIENDLY_ATTRIBUTE", } -var syncModes = []string{ - "IMPORT", - "FORCE", - "LEGACY", -} - func resourceKeycloakSamlIdentityProvider() *schema.Resource { samlSchema := map[string]*schema.Schema{ "backchannel_supported": { @@ -144,19 +139,6 @@ func resourceKeycloakSamlIdentityProvider() *schema.Resource { Default: "", Description: "Principal Attribute", }, - "gui_order": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "GUI Order", - }, - "sync_mode": { - Type: schema.TypeString, - Optional: true, - Default: "", - ValidateFunc: validation.StringInSlice(syncModes, false), - Description: "Sync Mode", - }, } samlResource := resourceKeycloakIdentityProvider() samlResource.Schema = mergeSchemas(samlResource.Schema, samlSchema) @@ -167,9 +149,10 @@ func resourceKeycloakSamlIdentityProvider() *schema.Resource { } func getSamlIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { - rec, _ := getIdentityProviderFromData(data) + rec, defaultConfig := getIdentityProviderFromData(data) rec.ProviderId = "saml" - rec.Config = &keycloak.IdentityProviderConfig{ + + samlIdentityProviderConfig := &keycloak.IdentityProviderConfig{ ValidateSignature: keycloak.KeycloakBoolQuoted(data.Get("validate_signature").(bool)), HideOnLoginPage: keycloak.KeycloakBoolQuoted(data.Get("hide_on_login_page").(bool)), BackchannelSupported: keycloak.KeycloakBoolQuoted(data.Get("backchannel_supported").(bool)), @@ -188,17 +171,24 @@ func getSamlIdentityProviderFromData(data *schema.ResourceData) (*keycloak.Ident WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(data.Get("want_assertions_encrypted").(bool)), PrincipalType: data.Get("principal_type").(string), PrincipalAttribute: data.Get("principal_attribute").(string), - GuiOrder: data.Get("gui_order").(string), - SyncMode: data.Get("sync_mode").(string), } + if _, ok := data.GetOk("signature_algorithm"); ok { - rec.Config.WantAuthnRequestsSigned = true + samlIdentityProviderConfig.WantAuthnRequestsSigned = true + } + + if err := mergo.Merge(samlIdentityProviderConfig, defaultConfig); err != nil { + return nil, err } + + rec.Config = samlIdentityProviderConfig + return rec, nil } func setSamlIdentityProviderData(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error { setIdentityProviderData(data, identityProvider) + data.Set("backchannel_supported", identityProvider.Config.BackchannelSupported) data.Set("validate_signature", identityProvider.Config.ValidateSignature) data.Set("hide_on_login_page", identityProvider.Config.HideOnLoginPage) @@ -217,7 +207,6 @@ func setSamlIdentityProviderData(data *schema.ResourceData, identityProvider *ke data.Set("want_assertions_encrypted", identityProvider.Config.WantAssertionsEncrypted) data.Set("principal_type", identityProvider.Config.PrincipalType) data.Set("principal_attribute", identityProvider.Config.PrincipalAttribute) - data.Set("gui_order", identityProvider.Config.GuiOrder) - data.Set("sync_mode", identityProvider.Config.SyncMode) + return nil } diff --git a/provider/resource_keycloak_saml_identity_provider_test.go b/provider/resource_keycloak_saml_identity_provider_test.go index eae9ef314..9cf65e5a4 100644 --- a/provider/resource_keycloak_saml_identity_provider_test.go +++ b/provider/resource_keycloak_saml_identity_provider_test.go @@ -2,6 +2,8 @@ package provider import ( "fmt" + "regexp" + "strconv" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -27,6 +29,43 @@ func TestAccKeycloakSamlIdentityProvider_basic(t *testing.T) { }) } +func TestAccKeycloakSamlIdentityProvider_extraConfig(t *testing.T) { + samlName := acctest.RandomWithPrefix("tf-acc") + customConfigValue := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakSamlIdentityProvider_extra_config(samlName, "dummyConfig", customConfigValue), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakSamlIdentityProviderHasCustomConfigValue("keycloak_saml_identity_provider.saml", customConfigValue), + ), + }, + }, + }) +} + +// ensure that extra_config keys which are covered by top-level attributes are not allowed +func TestAccKeycloakSamlIdentityProvider_extraConfigInvalid(t *testing.T) { + samlName := acctest.RandomWithPrefix("tf-acc") + customConfigValue := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakSamlIdentityProvider_extra_config(samlName, "syncMode", customConfigValue), + ExpectError: regexp.MustCompile("extra_config key \"syncMode\" is not allowed"), + }, + }, + }) +} + func TestAccKeycloakSamlIdentityProvider_createAfterManualDestroy(t *testing.T) { var saml = &keycloak.IdentityProvider{} @@ -118,6 +157,8 @@ func TestAccKeycloakSamlIdentityProvider_basicUpdateAll(t *testing.T) { ForceAuthn: keycloak.KeycloakBoolQuoted(firstForceAuthn), WantAssertionsSigned: keycloak.KeycloakBoolQuoted(firstAssertionsSigned), WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(firstAssertionsEncrypted), + GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)), + SyncMode: randomStringInSlice(syncModes), }, } @@ -142,6 +183,8 @@ func TestAccKeycloakSamlIdentityProvider_basicUpdateAll(t *testing.T) { ForceAuthn: keycloak.KeycloakBoolQuoted(!firstForceAuthn), WantAssertionsSigned: keycloak.KeycloakBoolQuoted(!firstAssertionsSigned), WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(!firstAssertionsEncrypted), + GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)), + SyncMode: randomStringInSlice(syncModes), }, } @@ -187,6 +230,21 @@ func testAccCheckKeycloakSamlIdentityProviderFetch(resourceName string, saml *ke } } +func testAccCheckKeycloakSamlIdentityProviderHasCustomConfigValue(resourceName, customConfigValue string) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedSaml, err := getKeycloakSamlIdentityProviderFromState(s, resourceName) + if err != nil { + return err + } + + if fetchedSaml.Config.ExtraConfig["dummyConfig"].(string) != customConfigValue { + return fmt.Errorf("expected custom saml provider to have config with a custom key 'dummyConfig' with a value %s, but value was %s", customConfigValue, fetchedSaml.Config.ExtraConfig["dummyConfig"].(string)) + } + + return nil + } +} + func testAccCheckKeycloakSamlIdentityProviderDestroy() resource.TestCheckFunc { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { @@ -226,27 +284,45 @@ func getKeycloakSamlIdentityProviderFromState(s *terraform.State, resourceName s func testKeycloakSamlIdentityProvider_basic(realm, saml string) string { return fmt.Sprintf(` -resource "keycloak_realm" "realm" { +data "keycloak_realm" "realm" { realm = "%s" } resource "keycloak_saml_identity_provider" "saml" { - realm = "${keycloak_realm.realm.id}" + realm = data.keycloak_realm.realm.id alias = "%s" entity_id = "https://example.com/entity_id" - single_sign_on_service_url = "https://example.com/auth" + single_sign_on_service_url = "https://example.com/auth" } `, realm, saml) } +func testKeycloakSamlIdentityProvider_extra_config(alias, configKey, configValue string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = data.keycloak_realm.realm.id + alias = "%s" + entity_id = "https://example.com/entity_id" + single_sign_on_service_url = "https://example.com/auth" + extra_config = { + %s = "%s" + } +} + `, testAccRealm.Realm, alias, configKey, configValue) +} + func testKeycloakSamlIdentityProvider_basicFromInterface(saml *keycloak.IdentityProvider) string { return fmt.Sprintf(` -resource "keycloak_realm" "realm" { +data "keycloak_realm" "realm" { realm = "%s" } resource "keycloak_saml_identity_provider" "saml" { - realm = "${keycloak_realm.realm.id}" + realm = data.keycloak_realm.realm.id alias = "%s" enabled = %t entity_id = "%s" @@ -265,6 +341,8 @@ resource "keycloak_saml_identity_provider" "saml" { force_authn = %t want_assertions_signed = %t want_assertions_encrypted = %t + gui_order = %s + sync_mode = "%s" } - `, saml.Realm, saml.Alias, saml.Enabled, saml.Config.EntityId, saml.Config.SingleSignOnServiceUrl, bool(saml.Config.BackchannelSupported), bool(saml.Config.ValidateSignature), bool(saml.Config.HideOnLoginPage), saml.Config.NameIDPolicyFormat, saml.Config.SingleLogoutServiceUrl, saml.Config.SigningCertificate, saml.Config.SignatureAlgorithm, saml.Config.XmlSignKeyInfoKeyNameTransformer, bool(saml.Config.PostBindingAuthnRequest), bool(saml.Config.PostBindingResponse), bool(saml.Config.PostBindingLogout), bool(saml.Config.ForceAuthn), bool(saml.Config.WantAssertionsSigned), bool(saml.Config.WantAssertionsEncrypted)) + `, saml.Realm, saml.Alias, saml.Enabled, saml.Config.EntityId, saml.Config.SingleSignOnServiceUrl, bool(saml.Config.BackchannelSupported), bool(saml.Config.ValidateSignature), bool(saml.Config.HideOnLoginPage), saml.Config.NameIDPolicyFormat, saml.Config.SingleLogoutServiceUrl, saml.Config.SigningCertificate, saml.Config.SignatureAlgorithm, saml.Config.XmlSignKeyInfoKeyNameTransformer, bool(saml.Config.PostBindingAuthnRequest), bool(saml.Config.PostBindingResponse), bool(saml.Config.PostBindingLogout), bool(saml.Config.ForceAuthn), bool(saml.Config.WantAssertionsSigned), bool(saml.Config.WantAssertionsEncrypted), saml.Config.GuiOrder, saml.Config.SyncMode) } From b0808fba5180d8ffeb70b3c966fb42ac656c6ccd Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Thu, 29 Apr 2021 19:09:33 -0500 Subject: [PATCH 2/4] docs --- docs/resources/oidc_google_identity_provider.md | 7 +++---- docs/resources/oidc_identity_provider.md | 2 ++ docs/resources/saml_identity_provider.md | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/resources/oidc_google_identity_provider.md b/docs/resources/oidc_google_identity_provider.md index 6add406d1..05a09c4c3 100644 --- a/docs/resources/oidc_google_identity_provider.md +++ b/docs/resources/oidc_google_identity_provider.md @@ -22,10 +22,7 @@ resource "keycloak_oidc_google_identity_provider" "google" { client_secret = var.google_identity_provider_client_secret trust_email = true hosted_domain = "example.com" - - extra_config = { - "syncMode" = "IMPORT" - } + sync_mode = "IMPORT" } ``` @@ -49,6 +46,8 @@ resource "keycloak_oidc_google_identity_provider" "google" { - `accepts_prompt_none_forward_from_client` - (Optional) When `true`, unauthenticated requests with `prompt=none` will be forwarded to Google instead of returning an error. Defaults to `false`. - `disable_user_info` - (Optional) When `true`, disables the usage of the user info service to obtain additional user information. Defaults to `false`. - `hide_on_login_page` - (Optional) When `true`, this identity provider will be hidden on the login page. Defaults to `false`. +- `sync_mode` - (Optional) The default sync mode to use for all mappers attached to this identity provider. Can be once of `IMPORT`, `FORCE`, or `LEGACY`. +- `gui_order` - (Optional) A number defining the order of this identity provider in the GUI. - `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. ## Attribute Reference diff --git a/docs/resources/oidc_identity_provider.md b/docs/resources/oidc_identity_provider.md index c02ac3b85..68704096c 100644 --- a/docs/resources/oidc_identity_provider.md +++ b/docs/resources/oidc_identity_provider.md @@ -58,6 +58,8 @@ resource "keycloak_oidc_identity_provider" "realm_identity_provider" { - `ui_locales` - (Optional) Pass current locale to identity provider. Defaults to `false`. - `accepts_prompt_none_forward_from_client` (Optional) When `true`, the IDP will accept forwarded authentication requests that contain the `prompt=none` query parameter. Defaults to `false`. - `default_scopes` - (Optional) The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to `openid`. +- `sync_mode` - (Optional) The default sync mode to use for all mappers attached to this identity provider. Can be once of `IMPORT`, `FORCE`, or `LEGACY`. +- `gui_order` - (Optional) A number defining the order of this identity provider in the GUI. - `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. - `clientAuthMethod` (Optional) The client authentication method. Since Keycloak 8, this is a required attribute if OIDC provider is created using the Keycloak GUI. It accepts the values `client_secret_post` (Client secret sent as post), `client_secret_basic` (Client secret sent as basic auth), `client_secret_jwt` (Client secret as jwt) and `private_key_jwt ` (JTW signed with private key) diff --git a/docs/resources/saml_identity_provider.md b/docs/resources/saml_identity_provider.md index 1a0dfc814..260eab3c0 100644 --- a/docs/resources/saml_identity_provider.md +++ b/docs/resources/saml_identity_provider.md @@ -63,6 +63,9 @@ resource "keycloak_saml_identity_provider" "realm_saml_identity_provider" { - `signing_certificate` - (Optional) Signing Certificate. - `signature_algorithm` - (Optional) Signing Algorithm. Defaults to empty. - `xml_sign_key_info_key_name_transformer` - (Optional) Sign Key Transformer. Defaults to empty. +- `sync_mode` - (Optional) The default sync mode to use for all mappers attached to this identity provider. Can be once of `IMPORT`, `FORCE`, or `LEGACY`. +- `gui_order` - (Optional) A number defining the order of this identity provider in the GUI. +- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. ## Import From b58f691f0f5a78b3ae29eb11d5a5f56757e8c995 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Thu, 29 Apr 2021 20:03:17 -0500 Subject: [PATCH 3/4] fix tests --- ...ce_keycloak_saml_identity_provider_test.go | 42 +++---------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/provider/resource_keycloak_saml_identity_provider_test.go b/provider/resource_keycloak_saml_identity_provider_test.go index 9cf65e5a4..6827a6200 100644 --- a/provider/resource_keycloak_saml_identity_provider_test.go +++ b/provider/resource_keycloak_saml_identity_provider_test.go @@ -13,7 +13,6 @@ import ( ) func TestAccKeycloakSamlIdentityProvider_basic(t *testing.T) { - realmName := acctest.RandomWithPrefix("tf-acc") samlName := acctest.RandomWithPrefix("tf-acc") resource.Test(t, resource.TestCase{ @@ -22,7 +21,7 @@ func TestAccKeycloakSamlIdentityProvider_basic(t *testing.T) { CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), Steps: []resource.TestStep{ { - Config: testKeycloakSamlIdentityProvider_basic(realmName, samlName), + Config: testKeycloakSamlIdentityProvider_basic(samlName), Check: testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), }, }, @@ -69,7 +68,6 @@ func TestAccKeycloakSamlIdentityProvider_extraConfigInvalid(t *testing.T) { func TestAccKeycloakSamlIdentityProvider_createAfterManualDestroy(t *testing.T) { var saml = &keycloak.IdentityProvider{} - realmName := acctest.RandomWithPrefix("tf-acc") samlName := acctest.RandomWithPrefix("tf-acc") resource.Test(t, resource.TestCase{ @@ -78,7 +76,7 @@ func TestAccKeycloakSamlIdentityProvider_createAfterManualDestroy(t *testing.T) CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), Steps: []resource.TestStep{ { - Config: testKeycloakSamlIdentityProvider_basic(realmName, samlName), + Config: testKeycloakSamlIdentityProvider_basic(samlName), Check: testAccCheckKeycloakSamlIdentityProviderFetch("keycloak_saml_identity_provider.saml", saml), }, { @@ -88,41 +86,13 @@ func TestAccKeycloakSamlIdentityProvider_createAfterManualDestroy(t *testing.T) t.Fatal(err) } }, - Config: testKeycloakSamlIdentityProvider_basic(realmName, samlName), + Config: testKeycloakSamlIdentityProvider_basic(samlName), Check: testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), }, }, }) } -func TestAccKeycloakSamlIdentityProvider_basicUpdateRealm(t *testing.T) { - firstRealm := acctest.RandomWithPrefix("tf-acc") - secondRealm := acctest.RandomWithPrefix("tf-acc") - samlName := acctest.RandomWithPrefix("tf-acc") - - resource.Test(t, resource.TestCase{ - ProviderFactories: testAccProviderFactories, - PreCheck: func() { testAccPreCheck(t) }, - CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), - Steps: []resource.TestStep{ - { - Config: testKeycloakSamlIdentityProvider_basic(firstRealm, samlName), - Check: resource.ComposeTestCheckFunc( - testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), - resource.TestCheckResourceAttr("keycloak_saml_identity_provider.saml", "realm", firstRealm), - ), - }, - { - Config: testKeycloakSamlIdentityProvider_basic(secondRealm, samlName), - Check: resource.ComposeTestCheckFunc( - testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), - resource.TestCheckResourceAttr("keycloak_saml_identity_provider.saml", "realm", secondRealm), - ), - }, - }, - }) -} - func TestAccKeycloakSamlIdentityProvider_basicUpdateAll(t *testing.T) { realmName := acctest.RandomWithPrefix("tf-acc") firstEnabled := randomBool() @@ -282,7 +252,7 @@ func getKeycloakSamlIdentityProviderFromState(s *terraform.State, resourceName s return saml, nil } -func testKeycloakSamlIdentityProvider_basic(realm, saml string) string { +func testKeycloakSamlIdentityProvider_basic(saml string) string { return fmt.Sprintf(` data "keycloak_realm" "realm" { realm = "%s" @@ -294,7 +264,7 @@ resource "keycloak_saml_identity_provider" "saml" { entity_id = "https://example.com/entity_id" single_sign_on_service_url = "https://example.com/auth" } - `, realm, saml) + `, testAccRealm.Realm, saml) } func testKeycloakSamlIdentityProvider_extra_config(alias, configKey, configValue string) string { @@ -344,5 +314,5 @@ resource "keycloak_saml_identity_provider" "saml" { gui_order = %s sync_mode = "%s" } - `, saml.Realm, saml.Alias, saml.Enabled, saml.Config.EntityId, saml.Config.SingleSignOnServiceUrl, bool(saml.Config.BackchannelSupported), bool(saml.Config.ValidateSignature), bool(saml.Config.HideOnLoginPage), saml.Config.NameIDPolicyFormat, saml.Config.SingleLogoutServiceUrl, saml.Config.SigningCertificate, saml.Config.SignatureAlgorithm, saml.Config.XmlSignKeyInfoKeyNameTransformer, bool(saml.Config.PostBindingAuthnRequest), bool(saml.Config.PostBindingResponse), bool(saml.Config.PostBindingLogout), bool(saml.Config.ForceAuthn), bool(saml.Config.WantAssertionsSigned), bool(saml.Config.WantAssertionsEncrypted), saml.Config.GuiOrder, saml.Config.SyncMode) + `, testAccRealm.Realm, saml.Alias, saml.Enabled, saml.Config.EntityId, saml.Config.SingleSignOnServiceUrl, bool(saml.Config.BackchannelSupported), bool(saml.Config.ValidateSignature), bool(saml.Config.HideOnLoginPage), saml.Config.NameIDPolicyFormat, saml.Config.SingleLogoutServiceUrl, saml.Config.SigningCertificate, saml.Config.SignatureAlgorithm, saml.Config.XmlSignKeyInfoKeyNameTransformer, bool(saml.Config.PostBindingAuthnRequest), bool(saml.Config.PostBindingResponse), bool(saml.Config.PostBindingLogout), bool(saml.Config.ForceAuthn), bool(saml.Config.WantAssertionsSigned), bool(saml.Config.WantAssertionsEncrypted), saml.Config.GuiOrder, saml.Config.SyncMode) } From 92b472be62cb87ce0620ae19bf1219ff851189f7 Mon Sep 17 00:00:00 2001 From: Michael Parker Date: Mon, 3 May 2021 09:47:37 -0500 Subject: [PATCH 4/4] docs fixes --- docs/resources/oidc_google_identity_provider.md | 6 +++++- docs/resources/oidc_identity_provider.md | 2 +- docs/resources/saml_identity_provider.md | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/resources/oidc_google_identity_provider.md b/docs/resources/oidc_google_identity_provider.md index 05a09c4c3..8d30cdb6b 100644 --- a/docs/resources/oidc_google_identity_provider.md +++ b/docs/resources/oidc_google_identity_provider.md @@ -23,6 +23,10 @@ resource "keycloak_oidc_google_identity_provider" "google" { trust_email = true hosted_domain = "example.com" sync_mode = "IMPORT" + + extra_config = { + "myCustomConfigKey" = "myValue" + } } ``` @@ -48,7 +52,7 @@ resource "keycloak_oidc_google_identity_provider" "google" { - `hide_on_login_page` - (Optional) When `true`, this identity provider will be hidden on the login page. Defaults to `false`. - `sync_mode` - (Optional) The default sync mode to use for all mappers attached to this identity provider. Can be once of `IMPORT`, `FORCE`, or `LEGACY`. - `gui_order` - (Optional) A number defining the order of this identity provider in the GUI. -- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. +- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. Use this attribute at your own risk, as custom attributes may conflict with top-level configuration attributes in future provider updates. ## Attribute Reference diff --git a/docs/resources/oidc_identity_provider.md b/docs/resources/oidc_identity_provider.md index 68704096c..6e2f0d84b 100644 --- a/docs/resources/oidc_identity_provider.md +++ b/docs/resources/oidc_identity_provider.md @@ -60,7 +60,7 @@ resource "keycloak_oidc_identity_provider" "realm_identity_provider" { - `default_scopes` - (Optional) The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to `openid`. - `sync_mode` - (Optional) The default sync mode to use for all mappers attached to this identity provider. Can be once of `IMPORT`, `FORCE`, or `LEGACY`. - `gui_order` - (Optional) A number defining the order of this identity provider in the GUI. -- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. +- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. Use this attribute at your own risk, as custom attributes may conflict with top-level configuration attributes in future provider updates. - `clientAuthMethod` (Optional) The client authentication method. Since Keycloak 8, this is a required attribute if OIDC provider is created using the Keycloak GUI. It accepts the values `client_secret_post` (Client secret sent as post), `client_secret_basic` (Client secret sent as basic auth), `client_secret_jwt` (Client secret as jwt) and `private_key_jwt ` (JTW signed with private key) ## Attribute Reference diff --git a/docs/resources/saml_identity_provider.md b/docs/resources/saml_identity_provider.md index 260eab3c0..2a7217b2d 100644 --- a/docs/resources/saml_identity_provider.md +++ b/docs/resources/saml_identity_provider.md @@ -65,7 +65,7 @@ resource "keycloak_saml_identity_provider" "realm_saml_identity_provider" { - `xml_sign_key_info_key_name_transformer` - (Optional) Sign Key Transformer. Defaults to empty. - `sync_mode` - (Optional) The default sync mode to use for all mappers attached to this identity provider. Can be once of `IMPORT`, `FORCE`, or `LEGACY`. - `gui_order` - (Optional) A number defining the order of this identity provider in the GUI. -- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. +- `extra_config` - (Optional) A map of key/value pairs to add extra configuration to this identity provider. This can be used for custom oidc provider implementations, or to add configuration that is not yet supported by this Terraform provider. Use this attribute at your own risk, as custom attributes may conflict with top-level configuration attributes in future provider updates. ## Import