diff --git a/example/main.tf b/example/main.tf index 6afd8874..d4f7d298 100644 --- a/example/main.tf +++ b/example/main.tf @@ -1,345 +1,446 @@ 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" } resource "keycloak_realm" "test" { - realm = "test" - enabled = true - display_name = "foo" + realm = "test" + enabled = true + display_name = "foo" - account_theme = "base" + account_theme = "base" - access_code_lifespan = "30m" + access_code_lifespan = "30m" } resource "keycloak_group" "foo" { - realm_id = "${keycloak_realm.test.id}" - name = "foo" + realm_id = "${keycloak_realm.test.id}" + name = "foo" } resource "keycloak_group" "nested_foo" { - realm_id = "${keycloak_realm.test.id}" - parent_id = "${keycloak_group.foo.id}" - name = "nested-foo" + realm_id = "${keycloak_realm.test.id}" + parent_id = "${keycloak_group.foo.id}" + name = "nested-foo" } resource "keycloak_group" "bar" { - realm_id = "${keycloak_realm.test.id}" - name = "bar" + realm_id = "${keycloak_realm.test.id}" + name = "bar" } resource "keycloak_user" "user" { - realm_id = "${keycloak_realm.test.id}" - username = "test-user" + realm_id = "${keycloak_realm.test.id}" + username = "test-user" - email = "test-user@fakedomain.com" - first_name = "Testy" - last_name = "Tester" + email = "test-user@fakedomain.com" + first_name = "Testy" + last_name = "Tester" } resource "keycloak_user" "another_user" { - realm_id = "${keycloak_realm.test.id}" - username = "another-test-user" + realm_id = "${keycloak_realm.test.id}" + username = "another-test-user" - email = "another-test-user@fakedomain.com" - first_name = "Testy" - last_name = "Tester" + email = "another-test-user@fakedomain.com" + first_name = "Testy" + last_name = "Tester" } resource "keycloak_user" "user_with_password" { - realm_id = "${keycloak_realm.test.id}" - username = "user-with-password" + realm_id = "${keycloak_realm.test.id}" + username = "user-with-password" - email = "user-with-password@fakedomain.com" - first_name = "Testy" - last_name = "Tester" - initial_password { - value = "my password" - temporary = false - } + email = "user-with-password@fakedomain.com" + first_name = "Testy" + last_name = "Tester" + + initial_password { + value = "my password" + temporary = false + } } resource "keycloak_group_memberships" "foo_members" { - realm_id = "${keycloak_realm.test.id}" - group_id = "${keycloak_group.foo.id}" + realm_id = "${keycloak_realm.test.id}" + group_id = "${keycloak_group.foo.id}" - members = [ - "${keycloak_user.user.username}", - "${keycloak_user.another_user.username}" - ] + members = [ + "${keycloak_user.user.username}", + "${keycloak_user.another_user.username}", + ] } resource "keycloak_openid_client" "test_client" { - client_id = "test-openid-client" - name = "test-openid-client" - realm_id = "${keycloak_realm.test.id}" - description = "a test openid client" + client_id = "test-openid-client" + name = "test-openid-client" + realm_id = "${keycloak_realm.test.id}" + description = "a test openid client" + + standard_flow_enabled = true - standard_flow_enabled = true + access_type = "CONFIDENTIAL" - access_type = "CONFIDENTIAL" - valid_redirect_uris = [ - "http://localhost:5555/callback" - ] + valid_redirect_uris = [ + "http://localhost:5555/callback", + ] - client_secret = "secret" + client_secret = "secret" } resource "keycloak_openid_client_scope" "test_default_client_scope" { - name = "test-default-client-scope" - realm_id = "${keycloak_realm.test.id}" + name = "test-default-client-scope" + realm_id = "${keycloak_realm.test.id}" - description = "test" - consent_screen_text = "hello" + description = "test" + consent_screen_text = "hello" } resource "keycloak_openid_client_scope" "test_optional_client_scope" { - name = "test-optional-client-scope" - realm_id = "${keycloak_realm.test.id}" + name = "test-optional-client-scope" + realm_id = "${keycloak_realm.test.id}" - description = "test" - consent_screen_text = "hello" + description = "test" + consent_screen_text = "hello" } resource "keycloak_openid_client_default_scopes" "default_client_scopes" { - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" - default_scopes = [ - "profile", - "email", - "roles", - "web-origins", - "${keycloak_openid_client_scope.test_default_client_scope.name}" - ] + default_scopes = [ + "profile", + "email", + "roles", + "web-origins", + "${keycloak_openid_client_scope.test_default_client_scope.name}", + ] } resource "keycloak_openid_client_optional_scopes" "optional_client_scopes" { - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" - optional_scopes = [ - "address", - "phone", - "offline_access", - "${keycloak_openid_client_scope.test_optional_client_scope.name}" - ] + optional_scopes = [ + "address", + "phone", + "offline_access", + "${keycloak_openid_client_scope.test_optional_client_scope.name}", + ] } resource "keycloak_ldap_user_federation" "openldap" { - name = "openldap" - realm_id = "${keycloak_realm.test.id}" + name = "openldap" + realm_id = "${keycloak_realm.test.id}" + + enabled = true + import_enabled = false + + username_ldap_attribute = "cn" + rdn_ldap_attribute = "cn" + uuid_ldap_attribute = "entryDN" - enabled = true - import_enabled = false + user_object_classes = [ + "simpleSecurityObject", + "organizationalRole", + ] - username_ldap_attribute = "cn" - rdn_ldap_attribute = "cn" - uuid_ldap_attribute = "entryDN" - user_object_classes = [ - "simpleSecurityObject", - "organizationalRole" - ] - connection_url = "ldap://openldap" - users_dn = "dc=example,dc=org" - bind_dn = "cn=admin,dc=example,dc=org" - bind_credential = "admin" + connection_url = "ldap://openldap" + users_dn = "dc=example,dc=org" + bind_dn = "cn=admin,dc=example,dc=org" + bind_credential = "admin" - connection_timeout = "5s" - read_timeout = "10s" + connection_timeout = "5s" + read_timeout = "10s" - cache_policy = "NO_CACHE" + cache_policy = "NO_CACHE" } resource "keycloak_ldap_user_attribute_mapper" "description_attr_mapper" { - name = "description-mapper" - realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" - ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" + name = "description-mapper" + realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" + ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" - user_model_attribute = "description" - ldap_attribute = "description" + user_model_attribute = "description" + ldap_attribute = "description" - always_read_value_from_ldap = false + always_read_value_from_ldap = false } resource "keycloak_ldap_group_mapper" "group_mapper" { - name = "group mapper" - realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" - ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" + name = "group mapper" + realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" + ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" - ldap_groups_dn = "dc=example,dc=org" - group_name_ldap_attribute = "cn" - group_object_classes = [ - "groupOfNames" - ] - membership_attribute_type = "DN" - membership_ldap_attribute = "member" - membership_user_ldap_attribute = "cn" - memberof_ldap_attribute = "memberOf" + ldap_groups_dn = "dc=example,dc=org" + group_name_ldap_attribute = "cn" + + group_object_classes = [ + "groupOfNames", + ] + + membership_attribute_type = "DN" + membership_ldap_attribute = "member" + membership_user_ldap_attribute = "cn" + memberof_ldap_attribute = "memberOf" } resource "keycloak_ldap_msad_user_account_control_mapper" "msad_uac_mapper" { - name = "uac-mapper1" - realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" - ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" + name = "uac-mapper1" + realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" + ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" } resource "keycloak_ldap_full_name_mapper" "full_name_mapper" { - name = "full-name-mapper" - realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" - ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" + name = "full-name-mapper" + realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}" + ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}" - ldap_full_name_attribute = "cn" - read_only = true + ldap_full_name_attribute = "cn" + read_only = true } resource "keycloak_custom_user_federation" "custom" { - name = "custom1" - realm_id = "master" - provider_id = "custom" + name = "custom1" + realm_id = "master" + provider_id = "custom" - enabled = true + enabled = true } resource "keycloak_openid_user_attribute_protocol_mapper" "map_user_attributes_client" { - name = "tf-test-open-id-user-attribute-protocol-mapper-client" - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" - user_attribute = "description" - claim_name = "description" + name = "tf-test-open-id-user-attribute-protocol-mapper-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" + user_attribute = "description" + claim_name = "description" } resource "keycloak_openid_user_attribute_protocol_mapper" "map_user_attributes_client_scope" { - name = "tf-test-open-id-user-attribute-protocol-mapper-client-scope" - realm_id = "${keycloak_realm.test.id}" - client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}" - user_attribute = "foo2" - claim_name = "bar2" + name = "tf-test-open-id-user-attribute-protocol-mapper-client-scope" + realm_id = "${keycloak_realm.test.id}" + client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}" + user_attribute = "foo2" + claim_name = "bar2" } resource "keycloak_openid_group_membership_protocol_mapper" "map_group_memberships_client" { - name = "tf-test-open-id-group-membership-protocol-mapper-client" - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" - claim_name = "bar" + name = "tf-test-open-id-group-membership-protocol-mapper-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" + claim_name = "bar" } resource "keycloak_openid_group_membership_protocol_mapper" "map_group_memberships_client_scope" { - name = "tf-test-open-id-group-membership-protocol-mapper-client-scope" - realm_id = "${keycloak_realm.test.id}" - client_scope_id = "${keycloak_openid_client_scope.test_optional_client_scope.id}" - claim_name = "bar2" + name = "tf-test-open-id-group-membership-protocol-mapper-client-scope" + realm_id = "${keycloak_realm.test.id}" + client_scope_id = "${keycloak_openid_client_scope.test_optional_client_scope.id}" + claim_name = "bar2" } resource "keycloak_openid_full_name_protocol_mapper" "map_full_names_client" { - name = "tf-test-open-id-full-name-protocol-mapper-client" - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" + name = "tf-test-open-id-full-name-protocol-mapper-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" } resource "keycloak_openid_full_name_protocol_mapper" "map_full_names_client_scope" { - name = "tf-test-open-id-full-name-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-full-name-protocol-mapper-client-scope" + realm_id = "${keycloak_realm.test.id}" + client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}" } resource "keycloak_openid_user_property_protocol_mapper" "map_user_properties_client" { - name = "tf-test-open-id-user-property-protocol-mapper-client" - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" - user_property = "foo" - claim_name = "bar" + name = "tf-test-open-id-user-property-protocol-mapper-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" + user_property = "foo" + claim_name = "bar" } resource "keycloak_openid_user_property_protocol_mapper" "map_user_properties_client_scope" { - name = "tf-test-open-id-user-property-protocol-mapper-client-scope" - realm_id = "${keycloak_realm.test.id}" - client_scope_id = "${keycloak_openid_client_scope.test_optional_client_scope.id}" - user_property = "foo2" - claim_name = "bar2" + name = "tf-test-open-id-user-property-protocol-mapper-client-scope" + realm_id = "${keycloak_realm.test.id}" + client_scope_id = "${keycloak_openid_client_scope.test_optional_client_scope.id}" + user_property = "foo2" + claim_name = "bar2" } resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_client" { - name = "tf-test-open-id-hardcoded-claim-protocol-mapper-client" - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" + name = "tf-test-open-id-hardcoded-claim-protocol-mapper-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" - claim_name = "foo" - claim_value = "bar" + claim_name = "foo" + claim_value = "bar" } resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_client_scope" { - name = "tf-test-open-id-hardcoded-claim-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-hardcoded-claim-protocol-mapper-client-scope" + realm_id = "${keycloak_realm.test.id}" + client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}" - claim_name = "foo" - claim_value = "bar" + claim_name = "foo" + claim_value = "bar" } resource "keycloak_openid_client" "bearer_only_client" { - client_id = "test-bearer-only-client" - name = "test-bearer-only-client" - realm_id = "${keycloak_realm.test.id}" - description = "a test openid client using bearer-only" + client_id = "test-bearer-only-client" + name = "test-bearer-only-client" + realm_id = "${keycloak_realm.test.id}" + description = "a test openid client using bearer-only" - access_type = "BEARER-ONLY" + access_type = "BEARER-ONLY" } resource "keycloak_openid_audience_protocol_mapper" "audience_client_scope" { - name = "tf-test-openid-audience-protocol-mapper-client-scope" - realm_id = "${keycloak_realm.test.id}" - client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}" + name = "tf-test-openid-audience-protocol-mapper-client-scope" + realm_id = "${keycloak_realm.test.id}" + client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}" - add_to_id_token = true - add_to_access_token = false + add_to_id_token = true + add_to_access_token = false - included_client_audience = "${keycloak_openid_client.bearer_only_client.client_id}" + included_client_audience = "${keycloak_openid_client.bearer_only_client.client_id}" } resource "keycloak_openid_audience_protocol_mapper" "audience_client" { - name = "tf-test-openid-audience-protocol-mapper-client" - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_openid_client.test_client.id}" + name = "tf-test-openid-audience-protocol-mapper-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_openid_client.test_client.id}" - add_to_id_token = false - add_to_access_token = true + add_to_id_token = false + add_to_access_token = true - included_custom_audience = "foo" + included_custom_audience = "foo" } resource "keycloak_saml_client" "saml_client" { - realm_id = "${keycloak_realm.test.id}" - client_id = "test-saml-client" - name = "test-saml-client" + realm_id = "${keycloak_realm.test.id}" + client_id = "test-saml-client" + name = "test-saml-client" - sign_documents = false - sign_assertions = true - include_authn_statement = true + sign_documents = false + sign_assertions = true + include_authn_statement = true - signing_certificate = "${file("../provider/misc/saml-cert.pem")}" - signing_private_key = "${file("../provider/misc/saml-key.pem")}" + signing_certificate = "${file("../provider/misc/saml-cert.pem")}" + signing_private_key = "${file("../provider/misc/saml-key.pem")}" } resource "keycloak_saml_user_attribute_protocol_mapper" "saml_user_attribute_mapper" { - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_saml_client.saml_client.id}" - name = "test-saml-user-attribute-mapper" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_saml_client.saml_client.id}" + name = "test-saml-user-attribute-mapper" - user_attribute = "user-attribute" - friendly_name = "friendly-name" - saml_attribute_name = "saml-attribute-name" - saml_attribute_name_format = "Unspecified" + user_attribute = "user-attribute" + friendly_name = "friendly-name" + saml_attribute_name = "saml-attribute-name" + saml_attribute_name_format = "Unspecified" } resource "keycloak_saml_user_property_protocol_mapper" "saml_user_property_mapper" { - realm_id = "${keycloak_realm.test.id}" - client_id = "${keycloak_saml_client.saml_client.id}" - name = "test-saml-user-property-mapper" + realm_id = "${keycloak_realm.test.id}" + client_id = "${keycloak_saml_client.saml_client.id}" + name = "test-saml-user-property-mapper" + + user_property = "email" + saml_attribute_name = "email" + saml_attribute_name_format = "Unspecified" +} + +resource keycloak_oidc_identity_provider oidc { + realm = "${keycloak_realm.test.id}" + alias = "oidc" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + +resource keycloak_attribute_importer_identity_provider_mapper oidc { + realm = "${keycloak_realm.test.id}" + name = "attributeImporter" + claim_name = "upn" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + user_attribute = "email" +} + +resource keycloak_attribute_to_role_identity_provider_mapper oidc { + realm = "${keycloak_realm.test.id}" + name = "attributeToRole" + claim_name = "upn" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + claim_value = "value" + role = "testRole" +} + +resource keycloak_user_template_importer_identity_provider_mapper oidc { + realm = "${keycloak_realm.test.id}" + name = "userTemplate" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + template = "$${ALIAS}/$${CLAIM.upn}" +} + +resource keycloak_hardcoded_role_identity_provider_mapper oidc { + realm = "${keycloak_realm.test.id}" + name = "hardcodedRole" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + role = "testrole" +} + +resource keycloak_hardcoded_attribute_identity_provider_mapper oidc { + realm = "${keycloak_realm.test.id}" + name = "hardcodedUserSessionAttribute" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + attribute_name = "attribute" + attribute_value = "value" + user_session = true +} + +resource keycloak_saml_identity_provider saml { + realm = "${keycloak_realm.test.id}" + alias = "saml" + single_sign_on_service_url = "https://example.com/auth" +} + +resource keycloak_attribute_importer_identity_provider_mapper saml { + realm = "${keycloak_realm.test.id}" + name = "Attribute: email" + attribute_name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + user_attribute = "email" +} + +resource keycloak_attribute_to_role_identity_provider_mapper saml { + realm = "${keycloak_realm.test.id}" + name = "attributeToRole" + attribute_name = "upn" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + attribute_value = "value" + role = "testRole" +} + +resource keycloak_user_template_importer_identity_provider_mapper saml { + realm = "${keycloak_realm.test.id}" + name = "userTemplate" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + template = "$${ALIAS}/$${NAMEID}" +} + +resource keycloak_hardcoded_role_identity_provider_mapper saml { + realm = "${keycloak_realm.test.id}" + name = "hardcodedRole" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + role = "testrole" +} - user_property = "email" - saml_attribute_name = "email" - saml_attribute_name_format = "Unspecified" +resource keycloak_hardcoded_attribute_identity_provider_mapper saml { + realm = "${keycloak_realm.test.id}" + name = "hardcodedAttribute" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + attribute_name = "attribute" + attribute_value = "value" + user_session = false } diff --git a/keycloak/identity_provider.go b/keycloak/identity_provider.go new file mode 100644 index 00000000..f536d55b --- /dev/null +++ b/keycloak/identity_provider.go @@ -0,0 +1,82 @@ +package keycloak + +import ( + "fmt" + "log" +) + +type IdentityProviderConfig struct { + Key string `json:"key,omitempty"` + HostIp string `json:"hostIp,omitempty"` + UseJwksUrl KeycloakBoolQuoted `json:"useJwksUrl,omitempty"` + ClientId string `json:"clientId,omitempty"` + ClientSecret string `json:"clientSecret,omitempty"` + DisableUserInfo KeycloakBoolQuoted `json:"disableUserInfo"` + HideOnLoginPage KeycloakBoolQuoted `json:"hideOnLoginPage"` + NameIDPolicyFormat string `json:"nameIDPolicyFormat,omitempty"` + SingleLogoutServiceUrl string `json:"singleLogoutServiceUrl,omitempty"` + SingleSignOnServiceUrl string `json:"singleSignOnServiceUrl,omitempty"` + SigningCertificate string `json:"signingCertificate,omitempty"` + SignatureAlgorithm string `json:"signatureAlgorithm,omitempty"` + XmlSignKeyInfoKeyNameTransformer string `json:"xmlSignKeyInfoKeyNameTransformer,omitempty"` + PostBindingAuthnRequest KeycloakBoolQuoted `json:"postBindingAuthnRequest,omitempty"` + PostBindingResponse KeycloakBoolQuoted `json:"postBindingResponse,omitempty"` + PostBindingLogout KeycloakBoolQuoted `json:"postBindingLogout,omitempty"` + ForceAuthn KeycloakBoolQuoted `json:"forceAuthn,omitempty"` + WantAuthnRequestsSigned KeycloakBoolQuoted `json:"wantAuthnRequestsSigned,omitempty"` + WantAssertionsSigned KeycloakBoolQuoted `json:"wantAssertionsSigned,omitempty"` + WantAssertionsEncrypted KeycloakBoolQuoted `json:"wantAssertionsEncrypted,omitempty"` + BackchannelSupported KeycloakBoolQuoted `json:"backchannelSupported,omitempty"` + ValidateSignature KeycloakBoolQuoted `json:"validateSignature,omitempty"` + AuthorizationUrl string `json:"authorizationUrl,omitempty"` + TokenUrl string `json:"tokenUrl,omitempty"` + LoginHint string `json:"loginHint,omitempty"` + UILocales KeycloakBoolQuoted `json:"uiLocales,omitempty"` +} + +type IdentityProvider struct { + Realm string `json:"-"` + InternalId string `json:"internalId,omitempty"` + Alias string `json:"alias"` + DisplayName string `json:"displayName"` + ProviderId string `json:"providerId"` + Enabled bool `json:"enabled"` + StoreToken bool `json:"storeToken"` + AddReadTokenRoleOnCreate bool `json:"addReadTokenRoleOnCreate"` + AuthenticateByDefault bool `json:"authenticateByDefault"` + LinkOnly bool `json:"linkOnly"` + TrustEmail bool `json:"trustEmail"` + FirstBrokerLoginFlowAlias string `json:"firstBrokerLoginFlowAlias"` + PostBrokerLoginFlowAlias string `json:"postBrokerLoginFlowAlias"` + Config *IdentityProviderConfig `json:"config"` +} + +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) + if err != nil { + return err + } + + return nil +} + +func (keycloakClient *KeycloakClient) GetIdentityProvider(realm, alias string) (*IdentityProvider, error) { + var identityProvider IdentityProvider + identityProvider.Realm = realm + + err := keycloakClient.get(fmt.Sprintf("/realms/%s/identity-provider/instances/%s", realm, alias), &identityProvider) + if err != nil { + return nil, err + } + + return &identityProvider, nil +} + +func (keycloakClient *KeycloakClient) UpdateIdentityProvider(identityProvider *IdentityProvider) error { + return keycloakClient.put(fmt.Sprintf("/realms/%s/identity-provider/instances/%s", identityProvider.Realm, identityProvider.Alias), identityProvider) +} + +func (keycloakClient *KeycloakClient) DeleteIdentityProvider(realm, alias string) error { + return keycloakClient.delete(fmt.Sprintf("/realms/%s/identity-provider/instances/%s", realm, alias)) +} diff --git a/keycloak/identity_provider_mapper.go b/keycloak/identity_provider_mapper.go new file mode 100644 index 00000000..a90d5d76 --- /dev/null +++ b/keycloak/identity_provider_mapper.go @@ -0,0 +1,61 @@ +package keycloak + +import ( + "fmt" + "log" +) + +type IdentityProviderMapperConfig struct { + UserAttribute string `json:"user.attribute,omitempty"` + Claim string `json:"claim,omitempty"` + ClaimValue string `json:"claim.value,omitempty"` + HardcodedAttribute string `json:"attribute,omitempty"` + Attribute string `json:"attribute.name,omitempty"` + AttributeValue string `json:"attribute.value,omitempty"` + AttributeFriendlyName string `json:"attribute.friendly.name,omitempty"` + Template string `json:"template,omitempty"` + Role string `json:"role,omitempty"` +} + +type IdentityProviderMapper struct { + Realm string `json:"-"` + Provider string `json:"-"` + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + IdentityProviderAlias string `json:"identityProviderAlias,omitempty"` + IdentityProviderMapper string `json:"identityProviderMapper,omitempty"` + Config *IdentityProviderMapperConfig `json:"config,omitempty"` +} + +func (keycloakClient *KeycloakClient) NewIdentityProviderMapper(identityProviderMapper *IdentityProviderMapper) error { + log.Printf("[WARN] Realm: %s", identityProviderMapper.Realm) + location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/mappers", identityProviderMapper.Realm, identityProviderMapper.IdentityProviderAlias), identityProviderMapper) + if err != nil { + return err + } + + identityProviderMapper.Id = getIdFromLocationHeader(location) + + return nil +} + +func (keycloakClient *KeycloakClient) GetIdentityProviderMapper(realm, alias, id string) (*IdentityProviderMapper, error) { + var identityProviderMapper IdentityProviderMapper + identityProviderMapper.Realm = realm + identityProviderMapper.IdentityProviderAlias = alias + + err := keycloakClient.get(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/mappers/%s", realm, alias, id), &identityProviderMapper) + if err != nil { + return nil, err + } + + return &identityProviderMapper, nil +} + +func (keycloakClient *KeycloakClient) UpdateIdentityProviderMapper(identityProviderMapper *IdentityProviderMapper) error { + return keycloakClient.put(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/mappers/%s", identityProviderMapper.Realm, identityProviderMapper.IdentityProviderAlias, identityProviderMapper.Id), identityProviderMapper) +} + +func (keycloakClient *KeycloakClient) DeleteIdentityProviderMapper(realm, alias, id string) error { + return keycloakClient.delete(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/mappers/%s", realm, alias, id)) +} diff --git a/keycloak/util.go b/keycloak/util.go index e1734f22..d4fe394c 100644 --- a/keycloak/util.go +++ b/keycloak/util.go @@ -1,11 +1,44 @@ package keycloak import ( + "bytes" "strconv" "strings" "time" ) +type KeycloakBoolQuoted bool + +func (c KeycloakBoolQuoted) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + if c == false { + buf.WriteString(`""`) + } else { + buf.WriteString(strconv.Quote(strconv.FormatBool(bool(c)))) + } + return buf.Bytes(), nil +} + +func (c *KeycloakBoolQuoted) UnmarshalJSON(in []byte) error { + value := string(in) + if value == `""` { + *c = false + return nil + } + unquoted, err := strconv.Unquote(value) + if err != nil { + return err + } + var b bool + b, err = strconv.ParseBool(unquoted) + if err != nil { + return err + } + res := KeycloakBoolQuoted(b) + *c = res + return nil +} + func getIdFromLocationHeader(locationHeader string) string { parts := strings.Split(locationHeader, "/") diff --git a/provider/generic_keycloak_identity_provider.go b/provider/generic_keycloak_identity_provider.go new file mode 100644 index 00000000..65059ad0 --- /dev/null +++ b/provider/generic_keycloak_identity_provider.go @@ -0,0 +1,195 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "strings" +) + +type identityProviderDataGetterFunc func(data *schema.ResourceData) (*keycloak.IdentityProvider, error) +type identityProviderDataSetterFunc func(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error + +func resourceKeycloakIdentityProvider() *schema.Resource { + return &schema.Resource{ + Delete: resourceKeycloakIdentityProviderDelete, + Importer: &schema.ResourceImporter{ + State: resourceKeycloakIdentityProviderImport, + }, + Schema: map[string]*schema.Schema{ + "alias": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The alias uniquely identifies an identity provider and it is also used to build the redirect uri.", + }, + "realm": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Realm Name", + }, + "internal_id": { + Type: schema.TypeString, + Computed: true, + Description: "Internal Identity Provider Id", + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "Friendly name for Identity Providers.", + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Enable/disable this identity provider.", + }, + "store_token": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Enable/disable if tokens must be stored after authenticating users.", + }, + "add_read_token_role_on_create": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + Description: "Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.", + }, + "authenticate_by_default": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Enable/disable authenticate users by default.", + }, + "link_only": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "If true, users cannot log in through this provider. They can only link to this provider. This is useful if you don't want to allow login from the provider, but want to integrate with a provider", + }, + "trust_email": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "If enabled then email provided by this provider is not verified even if verification is enabled for the realm.", + }, + "first_broker_login_flow_alias": { + Type: schema.TypeString, + Optional: true, + Default: "first broker login", + Description: "Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account.", + }, + "post_broker_login_flow_alias": { + Type: schema.TypeString, + Optional: true, + 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.", + }, + }, + } +} + +func getIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { + rec := &keycloak.IdentityProvider{ + Realm: data.Get("realm").(string), + Alias: data.Get("alias").(string), + DisplayName: data.Get("display_name").(string), + Enabled: data.Get("enabled").(bool), + StoreToken: data.Get("store_token").(bool), + AddReadTokenRoleOnCreate: data.Get("add_read_token_role_on_create").(bool), + AuthenticateByDefault: data.Get("authenticate_by_default").(bool), + LinkOnly: data.Get("link_only").(bool), + TrustEmail: data.Get("trust_email").(bool), + 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 +} + +func setIdentityProviderData(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error { + data.SetId(identityProvider.Alias) + data.Set("internal_id", identityProvider.InternalId) + data.Set("realm", identityProvider.Realm) + data.Set("alias", identityProvider.Alias) + data.Set("display_name", identityProvider.DisplayName) + data.Set("enabled", identityProvider.Enabled) + data.Set("store_token", identityProvider.StoreToken) + data.Set("add_read_token_role_on_create", identityProvider.AddReadTokenRoleOnCreate) + data.Set("authenticate_by_default", identityProvider.AuthenticateByDefault) + data.Set("link_only", identityProvider.LinkOnly) + 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 +} + +func resourceKeycloakIdentityProviderDelete(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realm := data.Get("realm").(string) + alias := data.Get("alias").(string) + + return keycloakClient.DeleteIdentityProvider(realm, alias) +} + +func resourceKeycloakIdentityProviderImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid import. Supported import formats: {{realm}}/{{identityProviderAlias}}") + } + + d.Set("realm", parts[0]) + d.SetId(parts[1]) + + return []*schema.ResourceData{d}, nil +} + +func resourceKeycloakIdentityProviderCreate(getIdentityProviderFromData identityProviderDataGetterFunc, setDataFromIdentityProvider identityProviderDataSetterFunc) func(data *schema.ResourceData, meta interface{}) error { + return func(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + identityProvider, err := getIdentityProviderFromData(data) + if err = keycloakClient.NewIdentityProvider(identityProvider); err != nil { + return err + } + if err = setDataFromIdentityProvider(data, identityProvider); err != nil { + return err + } + return resourceKeycloakIdentityProviderRead(setDataFromIdentityProvider)(data, meta) + } +} + +func resourceKeycloakIdentityProviderRead(setDataFromIdentityProvider identityProviderDataSetterFunc) func(data *schema.ResourceData, meta interface{}) error { + return func(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + realm := data.Get("realm").(string) + alias := data.Get("alias").(string) + identityProvider, err := keycloakClient.GetIdentityProvider(realm, alias) + if err != nil { + return handleNotFoundError(err, data) + } + if err = setDataFromIdentityProvider(data, identityProvider); err != nil { + return err + } + return nil + } +} + +func resourceKeycloakIdentityProviderUpdate(getIdentityProviderFromData identityProviderDataGetterFunc, setDataFromIdentityProvider identityProviderDataSetterFunc) func(data *schema.ResourceData, meta interface{}) error { + return func(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + identityProvider, err := getIdentityProviderFromData(data) + if err = keycloakClient.UpdateIdentityProvider(identityProvider); err != nil { + return err + } + if err = setDataFromIdentityProvider(data, identityProvider); err != nil { + return err + } + return nil + } +} diff --git a/provider/generic_keycloak_identity_provider_mapper.go b/provider/generic_keycloak_identity_provider_mapper.go new file mode 100644 index 00000000..8bf7acc0 --- /dev/null +++ b/provider/generic_keycloak_identity_provider_mapper.go @@ -0,0 +1,131 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "strings" +) + +type identityProviderMapperDataGetterFunc func(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) +type identityProviderMapperDataSetterFunc func(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error + +func resourceKeycloakIdentityProviderMapper() *schema.Resource { + return &schema.Resource{ + Delete: resourceKeycloakIdentityProviderMapperDelete, + Importer: &schema.ResourceImporter{ + State: resourceKeycloakIdentityProviderMapperImport, + }, + Schema: map[string]*schema.Schema{ + "realm": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Realm Name", + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "IDP Mapper Name", + }, + "identity_provider_alias": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "IDP Alias", + }, + }, + } +} + +func getIdentityProviderMapperFromData(data *schema.ResourceData) (*keycloak.IdentityProviderMapper, error) { + rec := &keycloak.IdentityProviderMapper{ + Id: data.Id(), + Realm: data.Get("realm").(string), + Name: data.Get("name").(string), + IdentityProviderAlias: data.Get("identity_provider_alias").(string), + } + return rec, nil +} + +func setIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { + data.SetId(identityProviderMapper.Id) + data.Set("id", identityProviderMapper.Id) + data.Set("realm", identityProviderMapper.Realm) + data.Set("name", identityProviderMapper.Name) + data.Set("identity_provider_alias", identityProviderMapper.IdentityProviderAlias) + return nil +} + +func resourceKeycloakIdentityProviderMapperDelete(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realm := data.Get("realm").(string) + alias := data.Get("identity_provider_alias").(string) + id := data.Id() + + return keycloakClient.DeleteIdentityProviderMapper(realm, alias, id) +} + +func resourceKeycloakIdentityProviderMapperImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + + if len(parts) != 3 { + return nil, fmt.Errorf("Invalid import. Supported import formats: {{realm}}/{{identityProviderAlias}}/{{identityProviderMapperId}}") + } + + d.Set("realm", parts[0]) + d.Set("identity_provider_alias", parts[1]) + d.SetId(parts[2]) + + return []*schema.ResourceData{d}, nil +} + +func resourceKeycloakIdentityProviderMapperCreate(getIdentityProviderMapperFromData identityProviderMapperDataGetterFunc, setDataFromIdentityProviderMapper identityProviderMapperDataSetterFunc) func(data *schema.ResourceData, meta interface{}) error { + return func(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + identityProvider, err := getIdentityProviderMapperFromData(data, meta) + if err != nil { + return err + } + if err = keycloakClient.NewIdentityProviderMapper(identityProvider); err != nil { + return err + } + if err = setDataFromIdentityProviderMapper(data, identityProvider); err != nil { + return err + } + return resourceKeycloakIdentityProviderMapperRead(setDataFromIdentityProviderMapper)(data, meta) + } +} + +func resourceKeycloakIdentityProviderMapperRead(setDataFromIdentityProviderMapper identityProviderMapperDataSetterFunc) func(data *schema.ResourceData, meta interface{}) error { + return func(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + realm := data.Get("realm").(string) + alias := data.Get("identity_provider_alias").(string) + id := data.Id() + identityProvider, err := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if err != nil { + return handleNotFoundError(err, data) + } + if err = setDataFromIdentityProviderMapper(data, identityProvider); err != nil { + return err + } + return nil + } +} + +func resourceKeycloakIdentityProviderMapperUpdate(getIdentityProviderMapperFromData identityProviderMapperDataGetterFunc, setDataFromIdentityProviderMapper identityProviderMapperDataSetterFunc) func(data *schema.ResourceData, meta interface{}) error { + return func(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + identityProvider, err := getIdentityProviderMapperFromData(data, meta) + if err = keycloakClient.UpdateIdentityProviderMapper(identityProvider); err != nil { + return err + } + if err = setDataFromIdentityProviderMapper(data, identityProvider); err != nil { + return err + } + return nil + } +} diff --git a/provider/keycloak_attribute_importer_identity_provider_mapper.go b/provider/keycloak_attribute_importer_identity_provider_mapper.go new file mode 100644 index 00000000..1ea6af4e --- /dev/null +++ b/provider/keycloak_attribute_importer_identity_provider_mapper.go @@ -0,0 +1,79 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakAttributeImporterIdentityProviderMapper() *schema.Resource { + mapperSchema := map[string]*schema.Schema{ + "user_attribute": { + Type: schema.TypeString, + Required: true, + Description: "User Attribute", + }, + "attribute_name": { + Type: schema.TypeString, + Optional: true, + Description: "Attribute Name", + ConflictsWith: []string{"attribute_friendly_name"}, + }, + "attribute_friendly_name": { + Type: schema.TypeString, + Optional: true, + Description: "Attribute Friendly Name", + ConflictsWith: []string{"attribute_name"}, + }, + "claim_name": { + Type: schema.TypeString, + Optional: true, + Description: "Claim Name", + }, + } + genericMapperResource := resourceKeycloakIdentityProviderMapper() + genericMapperResource.Schema = mergeSchemas(genericMapperResource.Schema, mapperSchema) + genericMapperResource.Create = resourceKeycloakIdentityProviderMapperCreate(getAttributeImporterIdentityProviderMapperFromData, setAttributeImporterIdentityProviderMapperData) + genericMapperResource.Read = resourceKeycloakIdentityProviderMapperRead(setAttributeImporterIdentityProviderMapperData) + genericMapperResource.Update = resourceKeycloakIdentityProviderMapperUpdate(getAttributeImporterIdentityProviderMapperFromData, setAttributeImporterIdentityProviderMapperData) + return genericMapperResource +} + +func getAttributeImporterIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) + 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), + } + if identityProvider.ProviderId == "saml" { + if attr, ok := data.GetOk("attribute_friendly_name"); ok { + rec.Config.AttributeFriendlyName = attr.(string) + } else if attr, ok := data.GetOk("attribute_name"); ok { + rec.Config.Attribute = attr.(string) + } else { + return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_importer_identity_provider_mapper: %s: either "attribute_name" or "attribute_friendly_name" should be set for %s identity provider`, data.Get("name").(string), identityProvider.ProviderId) + } + } else if identityProvider.ProviderId == "oidc" { + if _, ok := data.GetOk("claim_name"); !ok { + return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_importer_identity_provider_mapper: %s: "claim_name": should be set for %s identity provider`, data.Get("name").(string), identityProvider.ProviderId) + } + rec.Config.Claim = data.Get("claim_name").(string) + } 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 +} + +func setAttributeImporterIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { + setIdentityProviderMapperData(data, identityProviderMapper) + data.Set("attribute_name", identityProviderMapper.Config.Attribute) + data.Set("user_attribute", identityProviderMapper.Config.UserAttribute) + data.Set("attribute_friendly_name", identityProviderMapper.Config.AttributeFriendlyName) + data.Set("claim_name", identityProviderMapper.Config.Claim) + return nil +} diff --git a/provider/keycloak_attribute_importer_identity_provider_mapper_test.go b/provider/keycloak_attribute_importer_identity_provider_mapper_test.go new file mode 100644 index 00000000..1c123630 --- /dev/null +++ b/provider/keycloak_attribute_importer_identity_provider_mapper_test.go @@ -0,0 +1,252 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakAttributeImporterIdentityProviderMapper_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + userAttribute := "terraform-" + acctest.RandString(10) + claimName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeImporterIdentityProviderMapper_basic(realmName, alias, mapperName, userAttribute, claimName), + Check: testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakAttributeImporterIdentityProviderMapper_createAfterManualDestroy(t *testing.T) { + var mapper = &keycloak.IdentityProviderMapper{} + + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + userAttribute := "terraform-" + acctest.RandString(10) + claimName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeImporterIdentityProviderMapper_basic(realmName, alias, mapperName, userAttribute, claimName), + Check: testAccCheckKeycloakAttributeImporterIdentityProviderMapperFetch("keycloak_attribute_importer_identity_provider_mapper.oidc", mapper), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakAttributeImporterIdentityProviderMapper_basic(realmName, alias, mapperName, userAttribute, claimName), + Check: testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakAttributeImporterIdentityProviderMapper_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + userAttribute := "terraform-" + acctest.RandString(10) + claimName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeImporterIdentityProviderMapper_basic(firstRealm, alias, mapperName, userAttribute, claimName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_attribute_importer_identity_provider_mapper.oidc", "realm", firstRealm), + ), + }, + { + Config: testKeycloakAttributeImporterIdentityProviderMapper_basic(secondRealm, alias, mapperName, userAttribute, claimName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_attribute_importer_identity_provider_mapper.oidc", "realm", secondRealm), + ), + }, + }, + }) +} + +func TestAccKeycloakAttributeImporterIdentityProviderMapper_basicUpdateAll(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + identityProviderAliasName := "terraform-" + acctest.RandString(10) + + firstMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + UserAttribute: acctest.RandString(10), + Attribute: acctest.RandString(10), + }, + } + + secondMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + UserAttribute: acctest.RandString(10), + Attribute: acctest.RandString(10), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeImporterIdentityProviderMapper_basicFromInterface(firstMapper), + Check: testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.saml"), + }, + { + Config: testKeycloakAttributeImporterIdentityProviderMapper_basicFromInterface(secondMapper), + Check: testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists("keycloak_attribute_importer_identity_provider_mapper.saml"), + }, + }, + }) +} + +func testAccCheckKeycloakAttributeImporterIdentityProviderMapperExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakAttributeImporterIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakAttributeImporterIdentityProviderMapperFetch(resourceName string, mapper *keycloak.IdentityProviderMapper) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedMapper, err := getKeycloakAttributeImporterIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + mapper.IdentityProviderAlias = fetchedMapper.IdentityProviderAlias + mapper.Realm = fetchedMapper.Realm + mapper.Id = fetchedMapper.Id + + return nil + } +} + +func testAccCheckKeycloakAttributeImporterIdentityProviderMapperDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_attribute_importer_identity_provider_mapper" { + continue + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + mapper, _ := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if mapper != nil { + return fmt.Errorf("oidc config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakAttributeImporterIdentityProviderMapperFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + mapper, err := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if err != nil { + return nil, fmt.Errorf("error getting identity provider mapper config with id %s: %s", id, err) + } + + return mapper, nil +} + +func testKeycloakAttributeImporterIdentityProviderMapper_basic(realm, alias, name, userAttribute, claimName string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + +resource keycloak_attribute_importer_identity_provider_mapper oidc { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + user_attribute = "%s" + claim_name = "%s" +} + `, realm, alias, name, userAttribute, claimName) +} + +func testKeycloakAttributeImporterIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + single_sign_on_service_url = "https://example.com/auth" +} + +resource keycloak_attribute_importer_identity_provider_mapper saml { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + attribute_name = "%s" + user_attribute = "%s" +} + `, mapper.Realm, mapper.IdentityProviderAlias, mapper.Name, mapper.Config.Attribute, mapper.Config.UserAttribute) +} diff --git a/provider/keycloak_attribute_to_role_identity_provider_mapper.go b/provider/keycloak_attribute_to_role_identity_provider_mapper.go new file mode 100644 index 00000000..43e41278 --- /dev/null +++ b/provider/keycloak_attribute_to_role_identity_provider_mapper.go @@ -0,0 +1,99 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakAttributeToRoleIdentityProviderMapper() *schema.Resource { + mapperSchema := map[string]*schema.Schema{ + "attribute_name": { + Type: schema.TypeString, + Optional: true, + Description: "Attribute Name", + ConflictsWith: []string{"attribute_friendly_name"}, + }, + "attribute_value": { + Type: schema.TypeString, + Optional: true, + Description: "Attribute Value", + }, + "claim_name": { + Type: schema.TypeString, + Optional: true, + Description: "OIDC Claim Name", + }, + "claim_value": { + Type: schema.TypeString, + Optional: true, + Description: "OIDC Claim Value", + }, + "role": { + Type: schema.TypeString, + Required: true, + Description: "Role Name", + }, + "attribute_friendly_name": { + Type: schema.TypeString, + Optional: true, + Description: "Attribute Friendly Name", + ConflictsWith: []string{"attribute_name"}, + }, + } + genericMapperResource := resourceKeycloakIdentityProviderMapper() + genericMapperResource.Schema = mergeSchemas(genericMapperResource.Schema, mapperSchema) + genericMapperResource.Create = resourceKeycloakIdentityProviderMapperCreate(getAttributeToRoleIdentityProviderMapperFromData, setAttributeToRoleIdentityProviderMapperData) + genericMapperResource.Read = resourceKeycloakIdentityProviderMapperRead(setAttributeToRoleIdentityProviderMapperData) + genericMapperResource.Update = resourceKeycloakIdentityProviderMapperUpdate(getAttributeToRoleIdentityProviderMapperFromData, setAttributeToRoleIdentityProviderMapperData) + return genericMapperResource +} + +func getAttributeToRoleIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) + 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), + } + if identityProvider.ProviderId == "saml" { + if attr, ok := data.GetOk("attribute_friendly_name"); ok { + rec.Config.AttributeFriendlyName = attr.(string) + } else if attr, ok := data.GetOk("attribute_name"); ok { + rec.Config.Attribute = attr.(string) + } else { + return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_to_role_identity_provider_mapper: %s: either "attribute_name" or "attribute_friendly_name" should be set for %s identity provider`, data.Get("name").(string), identityProvider.ProviderId) + } + if _, ok := data.GetOk("attribute_value"); !ok { + return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_to_role_identity_provider_mapper: %s: "attribute_value": required field for %s identity provider`, data.Get("name").(string), identityProvider.ProviderId) + } + rec.Config.AttributeValue = data.Get("attribute_value").(string) + } else if identityProvider.ProviderId == "oidc" { + if _, ok := data.GetOk("claim_name"); !ok { + return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_to_role_identity_provider_mapper: %s: "claim_name": required field for %s identity provider`, data.Get("name").(string), identityProvider.ProviderId) + } + if _, ok := data.GetOk("claim_value"); !ok { + return nil, fmt.Errorf(`provider.keycloak: keycloak_attribute_to_role_identity_provider_mapper: %s: "claim_value": required field for %s identity provider`, data.Get("name").(string), identityProvider.ProviderId) + } + rec.Config.Claim = data.Get("claim_name").(string) + rec.Config.ClaimValue = data.Get("claim_value").(string) + } 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 +} + +func setAttributeToRoleIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { + setIdentityProviderMapperData(data, identityProviderMapper) + data.Set("role", identityProviderMapper.Config.Role) + data.Set("attribute_name", identityProviderMapper.Config.Attribute) + data.Set("attribute_value", identityProviderMapper.Config.AttributeValue) + data.Set("claim_name", identityProviderMapper.Config.Claim) + data.Set("claim_value", identityProviderMapper.Config.ClaimValue) + data.Set("attribute_friendly_name", identityProviderMapper.Config.AttributeFriendlyName) + return nil +} diff --git a/provider/keycloak_attribute_to_role_identity_provider_mapper_test.go b/provider/keycloak_attribute_to_role_identity_provider_mapper_test.go new file mode 100644 index 00000000..05df0658 --- /dev/null +++ b/provider/keycloak_attribute_to_role_identity_provider_mapper_test.go @@ -0,0 +1,259 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakAttributeToRoleIdentityProviderMapper_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + role := "terraform-" + acctest.RandString(10) + claimName := "terraform-" + acctest.RandString(10) + claimValue := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role, claimName, claimValue), + Check: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakAttributeToRoleIdentityProviderMapper_createAfterManualDestroy(t *testing.T) { + var mapper = &keycloak.IdentityProviderMapper{} + + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + role := "terraform-" + acctest.RandString(10) + claimName := "terraform-" + acctest.RandString(10) + claimValue := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role, claimName, claimValue), + Check: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperFetch("keycloak_attribute_to_role_identity_provider_mapper.oidc", mapper), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role, claimName, claimValue), + Check: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakAttributeToRoleIdentityProviderMapper_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + role := "terraform-" + acctest.RandString(10) + claimName := "terraform-" + acctest.RandString(10) + claimValue := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basic(firstRealm, alias, mapperName, role, claimName, claimValue), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_attribute_to_role_identity_provider_mapper.oidc", "realm", firstRealm), + ), + }, + { + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basic(secondRealm, alias, mapperName, role, claimName, claimValue), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_attribute_to_role_identity_provider_mapper.oidc", "realm", secondRealm), + ), + }, + }, + }) +} + +func TestAccKeycloakAttributeToRoleIdentityProviderMapper_basicUpdateAll(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + identityProviderAliasName := "terraform-" + acctest.RandString(10) + + firstMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + AttributeValue: acctest.RandString(10), + Attribute: acctest.RandString(10), + Role: acctest.RandString(10), + }, + } + + secondMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + AttributeValue: acctest.RandString(10), + Attribute: acctest.RandString(10), + Role: acctest.RandString(10), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basicFromInterface(firstMapper), + Check: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.saml"), + }, + { + Config: testKeycloakAttributeToRoleIdentityProviderMapper_basicFromInterface(secondMapper), + Check: testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists("keycloak_attribute_to_role_identity_provider_mapper.saml"), + }, + }, + }) +} + +func testAccCheckKeycloakAttributeToRoleIdentityProviderMapperExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakAttributeToRoleIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakAttributeToRoleIdentityProviderMapperFetch(resourceName string, mapper *keycloak.IdentityProviderMapper) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedMapper, err := getKeycloakAttributeToRoleIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + mapper.IdentityProviderAlias = fetchedMapper.IdentityProviderAlias + mapper.Realm = fetchedMapper.Realm + mapper.Id = fetchedMapper.Id + + return nil + } +} + +func testAccCheckKeycloakAttributeToRoleIdentityProviderMapperDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_attribute_to_role_identity_provider_mapper" { + continue + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + mapper, _ := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if mapper != nil { + return fmt.Errorf("oidc config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakAttributeToRoleIdentityProviderMapperFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + mapper, err := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if err != nil { + return nil, fmt.Errorf("error getting identity provider mapper config with id %s: %s", id, err) + } + + return mapper, nil +} + +func testKeycloakAttributeToRoleIdentityProviderMapper_basic(realm, alias, name, role, claimName, claimValue string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + +resource keycloak_attribute_to_role_identity_provider_mapper oidc { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + role = "%s" + claim_name = "%s" + claim_value = "%s" +} + `, realm, alias, name, role, claimName, claimValue) +} + +func testKeycloakAttributeToRoleIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + single_sign_on_service_url = "https://example.com/auth" +} + +resource keycloak_attribute_to_role_identity_provider_mapper saml { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + role = "%s" + attribute_name = "%s" + attribute_value = "%s" +} + `, mapper.Realm, mapper.IdentityProviderAlias, mapper.Name, mapper.Config.Role, mapper.Config.Attribute, mapper.Config.AttributeValue) +} diff --git a/provider/keycloak_hardcoded_attribute_identity_provider_mapper.go b/provider/keycloak_hardcoded_attribute_identity_provider_mapper.go new file mode 100644 index 00000000..288db9fd --- /dev/null +++ b/provider/keycloak_hardcoded_attribute_identity_provider_mapper.go @@ -0,0 +1,74 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakHardcodedAttributeIdentityProviderMapper() *schema.Resource { + mapperSchema := map[string]*schema.Schema{ + "attribute_name": { + Type: schema.TypeString, + Optional: true, + Description: "OIDC Claim", + }, + "attribute_value": { + Type: schema.TypeString, + Optional: true, + Description: "User Attribute", + }, + "user_session": { + Type: schema.TypeBool, + Required: true, + ForceNew: true, + Description: "Is Attribute Related To a User Session", + }, + } + genericMapperResource := resourceKeycloakIdentityProviderMapper() + genericMapperResource.Schema = mergeSchemas(genericMapperResource.Schema, mapperSchema) + genericMapperResource.Create = resourceKeycloakIdentityProviderMapperCreate(getHardcodedAttributeIdentityProviderMapperFromData, setHardcodedAttributeIdentityProviderMapperData) + genericMapperResource.Read = resourceKeycloakIdentityProviderMapperRead(setHardcodedAttributeIdentityProviderMapperData) + genericMapperResource.Update = resourceKeycloakIdentityProviderMapperUpdate(getHardcodedAttributeIdentityProviderMapperFromData, setHardcodedAttributeIdentityProviderMapperData) + return genericMapperResource +} + +func getHardcodedAttributeIdentityProviderMapperFromData(data *schema.ResourceData, _ interface{}) (*keycloak.IdentityProviderMapper, error) { + rec, _ := getIdentityProviderMapperFromData(data) + rec.IdentityProviderMapper = getHardcodedAttributeIdentityProviderMapperType(data.Get("user_session").(bool)) + rec.Config = &keycloak.IdentityProviderMapperConfig{ + HardcodedAttribute: data.Get("attribute_name").(string), + AttributeValue: data.Get("attribute_value").(string), + } + return rec, nil +} + +func setHardcodedAttributeIdentityProviderMapperData(data *schema.ResourceData, identityProviderMapper *keycloak.IdentityProviderMapper) error { + setIdentityProviderMapperData(data, identityProviderMapper) + data.Set("attribute_name", identityProviderMapper.Config.HardcodedAttribute) + data.Set("attribute_value", identityProviderMapper.Config.AttributeValue) + mapperType, err := getUserSessionFromHardcodedAttributeIdentityProviderMapperType(identityProviderMapper.IdentityProviderMapper) + if err != nil { + return err + } + data.Set("user_session", mapperType) + return nil +} + +func getHardcodedAttributeIdentityProviderMapperType(userSession bool) string { + if userSession { + return "hardcoded-user-session-attribute-idp-mapper" + } else { + return "hardcoded-attribute-idp-mapper" + } +} + +func getUserSessionFromHardcodedAttributeIdentityProviderMapperType(mapperType string) (bool, error) { + if mapperType == "hardcoded-user-session-attribute-idp-mapper" { + return true, nil + } else if mapperType == "hardcoded-attribute-idp-mapper" { + return false, nil + } else { + return false, fmt.Errorf(`provider.keycloak: keycloak_hardcoded_attribute_identity_provider_mapper: mapper type "%s" is not valid`, mapperType) + } +} diff --git a/provider/keycloak_hardcoded_attribute_identity_provider_mapper_test.go b/provider/keycloak_hardcoded_attribute_identity_provider_mapper_test.go new file mode 100644 index 00000000..3bb25012 --- /dev/null +++ b/provider/keycloak_hardcoded_attribute_identity_provider_mapper_test.go @@ -0,0 +1,260 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + attributeName := "terraform-" + acctest.RandString(10) + attributeValue := "terraform-" + acctest.RandString(10) + userSession := randomBool() + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basic(realmName, alias, mapperName, attributeName, attributeValue, userSession), + Check: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_createAfterManualDestroy(t *testing.T) { + var mapper = &keycloak.IdentityProviderMapper{} + + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + attributeName := "terraform-" + acctest.RandString(10) + attributeValue := "terraform-" + acctest.RandString(10) + userSession := randomBool() + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basic(realmName, alias, mapperName, attributeName, attributeValue, userSession), + Check: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperFetch("keycloak_hardcoded_attribute_identity_provider_mapper.oidc", mapper), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basic(realmName, alias, mapperName, attributeName, attributeValue, userSession), + Check: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + attributeName := "terraform-" + acctest.RandString(10) + attributeValue := "terraform-" + acctest.RandString(10) + userSession := randomBool() + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basic(firstRealm, alias, mapperName, attributeName, attributeValue, userSession), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_hardcoded_attribute_identity_provider_mapper.oidc", "realm", firstRealm), + ), + }, + { + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basic(secondRealm, alias, mapperName, attributeName, attributeValue, userSession), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_hardcoded_attribute_identity_provider_mapper.oidc", "realm", secondRealm), + ), + }, + }, + }) +} + +func TestAccKeycloakHardcodedAttributeIdentityProviderMapper_basicUpdateAll(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + identityProviderAliasName := "terraform-" + acctest.RandString(10) + userSession := randomBool() + + firstMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + IdentityProviderMapper: getHardcodedAttributeIdentityProviderMapperType(userSession), + Config: &keycloak.IdentityProviderMapperConfig{ + Attribute: acctest.RandString(10), + AttributeValue: acctest.RandString(10), + }, + } + + secondMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + IdentityProviderMapper: getHardcodedAttributeIdentityProviderMapperType(!userSession), + Config: &keycloak.IdentityProviderMapperConfig{ + Attribute: acctest.RandString(10), + AttributeValue: acctest.RandString(10), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basicFromInterface(firstMapper, userSession), + Check: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.saml"), + }, + { + Config: testKeycloakHardcodedAttributeIdentityProviderMapper_basicFromInterface(secondMapper, !userSession), + Check: testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists("keycloak_hardcoded_attribute_identity_provider_mapper.saml"), + }, + }, + }) +} + +func testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakHardcodedAttributeIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperFetch(resourceName string, mapper *keycloak.IdentityProviderMapper) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedMapper, err := getKeycloakHardcodedAttributeIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + mapper.IdentityProviderAlias = fetchedMapper.IdentityProviderAlias + mapper.Realm = fetchedMapper.Realm + mapper.Id = fetchedMapper.Id + + return nil + } +} + +func testAccCheckKeycloakHardcodedAttributeIdentityProviderMapperDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_hardcoded_attribute_identity_provider_mapper" { + continue + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + mapper, _ := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if mapper != nil { + return fmt.Errorf("oidc config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakHardcodedAttributeIdentityProviderMapperFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + mapper, err := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if err != nil { + return nil, fmt.Errorf("error getting identity provider mapper config with id %s: %s", id, err) + } + + return mapper, nil +} + +func testKeycloakHardcodedAttributeIdentityProviderMapper_basic(realm, alias, name, attributeName, attributeValue string, userSession bool) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + +resource keycloak_hardcoded_attribute_identity_provider_mapper oidc { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + attribute_name = "%s" + attribute_value = "%s" + user_session = %t +} + `, realm, alias, name, attributeName, attributeValue, userSession) +} + +func testKeycloakHardcodedAttributeIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper, userSession bool) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + single_sign_on_service_url = "https://example.com/auth" +} + +resource keycloak_hardcoded_attribute_identity_provider_mapper saml { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + attribute_name = "%s" + attribute_value = "%s" + user_session = %t +} + `, mapper.Realm, mapper.IdentityProviderAlias, mapper.Name, mapper.Config.Attribute, mapper.Config.AttributeValue, userSession) +} diff --git a/provider/keycloak_hardcoded_role_identity_provider_mapper.go b/provider/keycloak_hardcoded_role_identity_provider_mapper.go new file mode 100644 index 00000000..abd3b82c --- /dev/null +++ b/provider/keycloak_hardcoded_role_identity_provider_mapper.go @@ -0,0 +1,37 @@ +package provider + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakHardcodedRoleIdentityProviderMapper() *schema.Resource { + mapperSchema := map[string]*schema.Schema{ + "role": { + Type: schema.TypeString, + Optional: true, + Description: "Role Name", + }, + } + genericMapperResource := resourceKeycloakIdentityProviderMapper() + genericMapperResource.Schema = mergeSchemas(genericMapperResource.Schema, mapperSchema) + genericMapperResource.Create = resourceKeycloakIdentityProviderMapperCreate(getHardcodedRoleIdentityProviderMapperFromData, setHardcodedRoleIdentityProviderMapperData) + genericMapperResource.Read = resourceKeycloakIdentityProviderMapperRead(setHardcodedRoleIdentityProviderMapperData) + genericMapperResource.Update = resourceKeycloakIdentityProviderMapperUpdate(getHardcodedRoleIdentityProviderMapperFromData, setHardcodedRoleIdentityProviderMapperData) + return genericMapperResource +} + +func getHardcodedRoleIdentityProviderMapperFromData(data *schema.ResourceData, _ interface{}) (*keycloak.IdentityProviderMapper, error) { + rec, _ := getIdentityProviderMapperFromData(data) + rec.IdentityProviderMapper = "oidc-hardcoded-role-idp-mapper" + rec.Config = &keycloak.IdentityProviderMapperConfig{ + 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) + return nil +} diff --git a/provider/keycloak_hardcoded_role_identity_provider_mapper_test.go b/provider/keycloak_hardcoded_role_identity_provider_mapper_test.go new file mode 100644 index 00000000..5af28508 --- /dev/null +++ b/provider/keycloak_hardcoded_role_identity_provider_mapper_test.go @@ -0,0 +1,245 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakHardcodedRoleIdentityProviderMapper_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + role := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role), + Check: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakHardcodedRoleIdentityProviderMapper_createAfterManualDestroy(t *testing.T) { + var mapper = &keycloak.IdentityProviderMapper{} + + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + role := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role), + Check: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperFetch("keycloak_hardcoded_role_identity_provider_mapper.oidc", mapper), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basic(realmName, alias, mapperName, role), + Check: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakHardcodedRoleIdentityProviderMapper_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + role := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basic(firstRealm, alias, mapperName, role), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_hardcoded_role_identity_provider_mapper.oidc", "realm", firstRealm), + ), + }, + { + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basic(secondRealm, alias, mapperName, role), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_hardcoded_role_identity_provider_mapper.oidc", "realm", secondRealm), + ), + }, + }, + }) +} + +func TestAccKeycloakHardcodedRoleIdentityProviderMapper_basicUpdateAll(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + identityProviderAliasName := "terraform-" + acctest.RandString(10) + + firstMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + Role: acctest.RandString(10), + }, + } + + secondMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + Role: acctest.RandString(10), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basicFromInterface(firstMapper), + Check: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.saml"), + }, + { + Config: testKeycloakHardcodedRoleIdentityProviderMapper_basicFromInterface(secondMapper), + Check: testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists("keycloak_hardcoded_role_identity_provider_mapper.saml"), + }, + }, + }) +} + +func testAccCheckKeycloakHardcodedRoleIdentityProviderMapperExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakHardcodedRoleIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakHardcodedRoleIdentityProviderMapperFetch(resourceName string, mapper *keycloak.IdentityProviderMapper) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedMapper, err := getKeycloakHardcodedRoleIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + mapper.IdentityProviderAlias = fetchedMapper.IdentityProviderAlias + mapper.Realm = fetchedMapper.Realm + mapper.Id = fetchedMapper.Id + + return nil + } +} + +func testAccCheckKeycloakHardcodedRoleIdentityProviderMapperDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_hardcoded_role_identity_provider_mapper" { + continue + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + mapper, _ := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if mapper != nil { + return fmt.Errorf("oidc config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakHardcodedRoleIdentityProviderMapperFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + mapper, err := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if err != nil { + return nil, fmt.Errorf("error getting identity provider mapper config with id %s: %s", id, err) + } + + return mapper, nil +} + +func testKeycloakHardcodedRoleIdentityProviderMapper_basic(realm, alias, name, role string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + +resource keycloak_hardcoded_role_identity_provider_mapper oidc { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + role = "%s" +} + `, realm, alias, name, role) +} + +func testKeycloakHardcodedRoleIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + single_sign_on_service_url = "https://example.com/auth" +} + +resource keycloak_hardcoded_role_identity_provider_mapper saml { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + role = "%s" +} + `, mapper.Realm, mapper.IdentityProviderAlias, mapper.Name, mapper.Config.Role) +} diff --git a/provider/keycloak_oidc_identity_provider.go b/provider/keycloak_oidc_identity_provider.go new file mode 100644 index 00000000..92168b97 --- /dev/null +++ b/provider/keycloak_oidc_identity_provider.go @@ -0,0 +1,110 @@ +package provider + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakOidcIdentityProvider() *schema.Resource { + oidcSchema := map[string]*schema.Schema{ + "backchannel_supported": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Does the external IDP support backchannel logout?", + }, + "validate_signature": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Enable/disable signature validation of SAML responses.", + }, + "authorization_url": { + Type: schema.TypeString, + Required: true, + Description: "OIDC authorization URL.", + }, + "client_id": { + Type: schema.TypeString, + Required: true, + Description: "Client ID.", + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + Description: "Client Secret.", + }, + "disable_user_info": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Disable User Info.", + }, + "hide_on_login_page": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Hide On Login Page.", + }, + "token_url": { + Type: schema.TypeString, + Required: true, + Description: "Token URL.", + }, + "login_hint": { + Type: schema.TypeString, + Optional: true, + Description: "Login Hint.", + }, + "ui_locales": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Pass current locale to identity provider", + }, + } + oidcResource := resourceKeycloakIdentityProvider() + oidcResource.Schema = mergeSchemas(oidcResource.Schema, oidcSchema) + oidcResource.Create = resourceKeycloakIdentityProviderCreate(getOidcIdentityProviderFromData, setOidcIdentityProviderData) + oidcResource.Read = resourceKeycloakIdentityProviderRead(setOidcIdentityProviderData) + oidcResource.Update = resourceKeycloakIdentityProviderUpdate(getOidcIdentityProviderFromData, setOidcIdentityProviderData) + return oidcResource +} + +func getOidcIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { + rec, _ := getIdentityProviderFromData(data) + rec.ProviderId = "oidc" + rec.Config = &keycloak.IdentityProviderConfig{ + BackchannelSupported: keycloak.KeycloakBoolQuoted(data.Get("backchannel_supported").(bool)), + ValidateSignature: keycloak.KeycloakBoolQuoted(data.Get("validate_signature").(bool)), + AuthorizationUrl: data.Get("authorization_url").(string), + ClientId: data.Get("client_id").(string), + DisableUserInfo: keycloak.KeycloakBoolQuoted(data.Get("disable_user_info").(bool)), + HideOnLoginPage: keycloak.KeycloakBoolQuoted(data.Get("hide_on_login_page").(bool)), + TokenUrl: data.Get("token_url").(string), + UILocales: keycloak.KeycloakBoolQuoted(data.Get("ui_locales").(bool)), + LoginHint: data.Get("login_hint").(string), + } + + if data.HasChange("client_secret") { + rec.Config.ClientSecret = data.Get("client_secret").(string) + } + + return rec, nil +} + +func setOidcIdentityProviderData(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error { + setIdentityProviderData(data, identityProvider) + data.Set("backchannel_supported", identityProvider.Config.BackchannelSupported) + data.Set("use_jwks_url", identityProvider.Config.UseJwksUrl) + data.Set("validate_signature", identityProvider.Config.ValidateSignature) + data.Set("authorization_url", identityProvider.Config.AuthorizationUrl) + data.Set("client_id", identityProvider.Config.ClientId) + data.Set("disable_user_info", identityProvider.Config.DisableUserInfo) + data.Set("hide_on_login_page", identityProvider.Config.HideOnLoginPage) + data.Set("token_url", identityProvider.Config.TokenUrl) + data.Set("login_hint", identityProvider.Config.LoginHint) + data.Set("ui_locales", identityProvider.Config.UILocales) + return nil +} diff --git a/provider/keycloak_oidc_identity_provider_test.go b/provider/keycloak_oidc_identity_provider_test.go new file mode 100644 index 00000000..b731d032 --- /dev/null +++ b/provider/keycloak_oidc_identity_provider_test.go @@ -0,0 +1,232 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakOidcIdentityProvider_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + oidcName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOidcIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOidcIdentityProvider_basic(realmName, oidcName), + Check: testAccCheckKeycloakOidcIdentityProviderExists("keycloak_oidc_identity_provider.oidc"), + }, + }, + }) +} + +func TestAccKeycloakOidcIdentityProvider_createAfterManualDestroy(t *testing.T) { + var oidc = &keycloak.IdentityProvider{} + + realmName := "terraform-" + acctest.RandString(10) + oidcName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOidcIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOidcIdentityProvider_basic(realmName, oidcName), + Check: testAccCheckKeycloakOidcIdentityProviderFetch("keycloak_oidc_identity_provider.oidc", oidc), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProvider(oidc.Realm, oidc.Alias) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakOidcIdentityProvider_basic(realmName, oidcName), + Check: testAccCheckKeycloakOidcIdentityProviderExists("keycloak_oidc_identity_provider.oidc"), + }, + }, + }) +} + +func TestAccKeycloakOidcIdentityProvider_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + oidcName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOidcIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOidcIdentityProvider_basic(firstRealm, oidcName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakOidcIdentityProviderExists("keycloak_oidc_identity_provider.oidc"), + resource.TestCheckResourceAttr("keycloak_oidc_identity_provider.oidc", "realm", firstRealm), + ), + }, + { + Config: testKeycloakOidcIdentityProvider_basic(secondRealm, oidcName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakOidcIdentityProviderExists("keycloak_oidc_identity_provider.oidc"), + resource.TestCheckResourceAttr("keycloak_oidc_identity_provider.oidc", "realm", secondRealm), + ), + }, + }, + }) +} + +func TestAccKeycloakOidcIdentityProvider_basicUpdateAll(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + firstEnabled := randomBool() + + firstOidc := &keycloak.IdentityProvider{ + Realm: realmName, + Alias: acctest.RandString(10), + Enabled: firstEnabled, + Config: &keycloak.IdentityProviderConfig{ + AuthorizationUrl: "https://example.com/auth", + TokenUrl: "https://example.com/token", + ClientId: acctest.RandString(10), + ClientSecret: acctest.RandString(10), + }, + } + + secondOidc := &keycloak.IdentityProvider{ + Realm: realmName, + Alias: acctest.RandString(10), + Enabled: !firstEnabled, + Config: &keycloak.IdentityProviderConfig{ + AuthorizationUrl: "https://example.com/auth", + TokenUrl: "https://example.com/token", + ClientId: acctest.RandString(10), + ClientSecret: acctest.RandString(10), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOidcIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOidcIdentityProvider_basicFromInterface(firstOidc), + Check: testAccCheckKeycloakOidcIdentityProviderExists("keycloak_oidc_identity_provider.oidc"), + }, + { + Config: testKeycloakOidcIdentityProvider_basicFromInterface(secondOidc), + Check: testAccCheckKeycloakOidcIdentityProviderExists("keycloak_oidc_identity_provider.oidc"), + }, + }, + }) +} + +func testAccCheckKeycloakOidcIdentityProviderExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakOidcIdentityProviderFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakOidcIdentityProviderFetch(resourceName string, oidc *keycloak.IdentityProvider) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedOidc, err := getKeycloakOidcIdentityProviderFromState(s, resourceName) + if err != nil { + return err + } + + oidc.Alias = fetchedOidc.Alias + oidc.Realm = fetchedOidc.Realm + + return nil + } +} + +func testAccCheckKeycloakOidcIdentityProviderDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_oidc_identity_provider" { + continue + } + + id := rs.Primary.ID + realm := rs.Primary.Attributes["realm"] + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + oidc, _ := keycloakClient.GetIdentityProvider(realm, id) + if oidc != nil { + return fmt.Errorf("oidc config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakOidcIdentityProviderFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProvider, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["alias"] + + oidc, err := keycloakClient.GetIdentityProvider(realm, alias) + if err != nil { + return nil, fmt.Errorf("error getting oidc identity provider config with alias %s: %s", alias, err) + } + + return oidc, nil +} + +func testKeycloakOidcIdentityProvider_basic(realm, oidc string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + `, realm, oidc) +} + +func testKeycloakOidcIdentityProvider_basicFromInterface(oidc *keycloak.IdentityProvider) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + enabled = %t + authorization_url = "%s" + token_url = "%s" + client_id = "%s" + client_secret = "%s" +} + `, oidc.Realm, oidc.Alias, oidc.Enabled, oidc.Config.AuthorizationUrl, oidc.Config.TokenUrl, oidc.Config.ClientId, oidc.Config.ClientSecret) +} diff --git a/provider/keycloak_saml_identity_provider.go b/provider/keycloak_saml_identity_provider.go new file mode 100644 index 00000000..01334345 --- /dev/null +++ b/provider/keycloak_saml_identity_provider.go @@ -0,0 +1,172 @@ +package provider + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +var nameIdPolicyFormats = map[string]string{ + "Windows Domain Qualified Name": "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName", + "Persistent": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + "Email": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "Kerberos": "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos", + "X.509 Subject Name": "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", + "Unspecified": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", +} + +var signatureAlgorithms = []string{ + "RSA_SHA1", + "RSA_SHA256", + "RSA_SHA512", + "DSA_SHA1", +} + +var keyNameTransformers = []string{ + "NONE", + "KEY_ID", + "CERT_SUBJECT", +} + +func resourceKeycloakSamlIdentityProvider() *schema.Resource { + samlSchema := map[string]*schema.Schema{ + "backchannel_supported": { + Type: schema.TypeBool, + Optional: true, + Description: "Does the external IDP support backchannel logout?", + }, + "validate_signature": { + Type: schema.TypeBool, + Optional: true, + Description: "Enable/disable signature validation of SAML responses.", + }, + "hide_on_login_page": { + Type: schema.TypeBool, + Optional: true, + Description: "Hide On Login Page.", + }, + "name_id_policy_format": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice(keys(nameIdPolicyFormats), false), + StateFunc: func(value interface{}) string { + return nameIdPolicyFormats[value.(string)] + }, + Description: "Name ID Policy Format.", + }, + "single_logout_service_url": { + Type: schema.TypeString, + Optional: true, + Description: "Logout URL.", + }, + "single_sign_on_service_url": { + Type: schema.TypeString, + Required: true, + Description: "SSO Logout URL.", + }, + "signing_certificate": { + Type: schema.TypeString, + Optional: true, + Description: "Signing Certificate.", + }, + "signature_algorithm": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice(signatureAlgorithms, false), + Description: "Signing Algorithm.", + }, + "xml_sign_key_info_key_name_transformer": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice(keyNameTransformers, false), + Description: "Sign Key Transformer.", + }, + "post_binding_authn_request": { + Type: schema.TypeBool, + Optional: true, + Description: "Post Binding Authn Request.", + }, + "post_binding_response": { + Type: schema.TypeBool, + Optional: true, + Description: "Post Binding Response.", + }, + "post_binding_logout": { + Type: schema.TypeBool, + Optional: true, + Description: "Post Binding Logout.", + }, + "force_authn": { + Type: schema.TypeBool, + Optional: true, + Description: "Require Force Authn.", + }, + "want_assertions_signed": { + Type: schema.TypeBool, + Optional: true, + Description: "Want Assertions Signed.", + }, + "want_assertions_encrypted": { + Type: schema.TypeBool, + Optional: true, + Description: "Want Assertions Encrypted.", + }, + } + samlResource := resourceKeycloakIdentityProvider() + samlResource.Schema = mergeSchemas(samlResource.Schema, samlSchema) + samlResource.Create = resourceKeycloakIdentityProviderCreate(getSamlIdentityProviderFromData, setSamlIdentityProviderData) + samlResource.Read = resourceKeycloakIdentityProviderRead(setSamlIdentityProviderData) + samlResource.Update = resourceKeycloakIdentityProviderUpdate(getSamlIdentityProviderFromData, setSamlIdentityProviderData) + return samlResource +} + +func getSamlIdentityProviderFromData(data *schema.ResourceData) (*keycloak.IdentityProvider, error) { + rec, _ := getIdentityProviderFromData(data) + rec.ProviderId = "saml" + rec.Config = &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)), + NameIDPolicyFormat: nameIdPolicyFormats[data.Get("name_id_policy_format").(string)], + SingleLogoutServiceUrl: data.Get("single_logout_service_url").(string), + SingleSignOnServiceUrl: data.Get("single_sign_on_service_url").(string), + SigningCertificate: data.Get("signing_certificate").(string), + SignatureAlgorithm: data.Get("signature_algorithm").(string), + XmlSignKeyInfoKeyNameTransformer: data.Get("xml_sign_key_info_key_name_transformer").(string), + PostBindingAuthnRequest: keycloak.KeycloakBoolQuoted(data.Get("post_binding_authn_request").(bool)), + PostBindingResponse: keycloak.KeycloakBoolQuoted(data.Get("post_binding_response").(bool)), + PostBindingLogout: keycloak.KeycloakBoolQuoted(data.Get("post_binding_logout").(bool)), + ForceAuthn: keycloak.KeycloakBoolQuoted(data.Get("force_authn").(bool)), + WantAssertionsSigned: keycloak.KeycloakBoolQuoted(data.Get("want_assertions_signed").(bool)), + WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(data.Get("want_assertions_encrypted").(bool)), + } + if _, ok := data.GetOk("signature_algorithm"); ok { + rec.Config.WantAuthnRequestsSigned = true + } + return rec, nil +} + +func setSamlIdentityProviderData(data *schema.ResourceData, identityProvider *keycloak.IdentityProvider) error { + setIdentityProviderData(data, identityProvider) + data.Set("backchannel_supported", identityProvider.Config.BackchannelSupported) + data.Set("use_jwks_url", identityProvider.Config.UseJwksUrl) + data.Set("validate_signature", identityProvider.Config.ValidateSignature) + data.Set("hide_on_login_page", identityProvider.Config.HideOnLoginPage) + data.Set("name_id_policy_format", identityProvider.Config.NameIDPolicyFormat) + data.Set("single_logout_service_url", identityProvider.Config.SingleLogoutServiceUrl) + data.Set("single_sign_on_service_url", identityProvider.Config.SingleSignOnServiceUrl) + data.Set("signing_certificate", identityProvider.Config.SigningCertificate) + data.Set("signature_algorithm", identityProvider.Config.SignatureAlgorithm) + data.Set("xml_sign_key_info_key_name_transformer", identityProvider.Config.XmlSignKeyInfoKeyNameTransformer) + data.Set("post_binding_authn_request", identityProvider.Config.PostBindingAuthnRequest) + data.Set("post_binding_response", identityProvider.Config.PostBindingResponse) + data.Set("post_binding_logout", identityProvider.Config.PostBindingLogout) + data.Set("force_authn", identityProvider.Config.ForceAuthn) + data.Set("want_authn_requests_signed", identityProvider.Config.WantAuthnRequestsSigned) + data.Set("want_assertions_signed", identityProvider.Config.WantAssertionsSigned) + data.Set("want_assertions_encrypted", identityProvider.Config.WantAssertionsEncrypted) + return nil +} diff --git a/provider/keycloak_saml_identity_provider_test.go b/provider/keycloak_saml_identity_provider_test.go new file mode 100644 index 00000000..99279e5d --- /dev/null +++ b/provider/keycloak_saml_identity_provider_test.go @@ -0,0 +1,271 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakSamlIdentityProvider_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + samlName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakSamlIdentityProvider_basic(realmName, samlName), + Check: testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), + }, + }, + }) +} + +func TestAccKeycloakSamlIdentityProvider_createAfterManualDestroy(t *testing.T) { + var saml = &keycloak.IdentityProvider{} + + realmName := "terraform-" + acctest.RandString(10) + samlName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakSamlIdentityProvider_basic(realmName, samlName), + Check: testAccCheckKeycloakSamlIdentityProviderFetch("keycloak_saml_identity_provider.saml", saml), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProvider(saml.Realm, saml.Alias) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakSamlIdentityProvider_basic(realmName, samlName), + Check: testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), + }, + }, + }) +} + +func TestAccKeycloakSamlIdentityProvider_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + samlName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + 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 := "terraform-" + acctest.RandString(10) + firstEnabled := randomBool() + firstBackchannel := randomBool() + firstValidateSignature := randomBool() + firstHideOnLogin := randomBool() + firstForceAuthn := randomBool() + firstAssertionsEncrypted := randomBool() + firstAssertionsSigned := randomBool() + firstPostBindingLogout := randomBool() + firstPostBindingResponse := randomBool() + firstPostBindingRequest := randomBool() + + firstSaml := &keycloak.IdentityProvider{ + Realm: realmName, + Alias: acctest.RandString(10), + Enabled: firstEnabled, + Config: &keycloak.IdentityProviderConfig{ + SingleSignOnServiceUrl: "https://example.com/signon/2", + BackchannelSupported: keycloak.KeycloakBoolQuoted(firstBackchannel), + ValidateSignature: keycloak.KeycloakBoolQuoted(firstValidateSignature), + HideOnLoginPage: keycloak.KeycloakBoolQuoted(firstHideOnLogin), + NameIDPolicyFormat: "Email", + SingleLogoutServiceUrl: "https://example.com/logout/2", + SigningCertificate: acctest.RandString(10), + SignatureAlgorithm: "RSA_SHA512", + XmlSignKeyInfoKeyNameTransformer: "KEY_ID", + PostBindingAuthnRequest: keycloak.KeycloakBoolQuoted(firstPostBindingRequest), + PostBindingResponse: keycloak.KeycloakBoolQuoted(firstPostBindingResponse), + PostBindingLogout: keycloak.KeycloakBoolQuoted(firstPostBindingLogout), + ForceAuthn: keycloak.KeycloakBoolQuoted(firstForceAuthn), + WantAssertionsSigned: keycloak.KeycloakBoolQuoted(firstAssertionsSigned), + WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(firstAssertionsEncrypted), + }, + } + + secondSaml := &keycloak.IdentityProvider{ + Realm: realmName, + Alias: acctest.RandString(10), + Enabled: !firstEnabled, + Config: &keycloak.IdentityProviderConfig{ + SingleSignOnServiceUrl: "https://example.com/signon/2", + BackchannelSupported: keycloak.KeycloakBoolQuoted(!firstBackchannel), + ValidateSignature: keycloak.KeycloakBoolQuoted(!firstValidateSignature), + HideOnLoginPage: keycloak.KeycloakBoolQuoted(!firstHideOnLogin), + NameIDPolicyFormat: "Persistent", + SingleLogoutServiceUrl: "https://example.com/logout/2", + SigningCertificate: acctest.RandString(10), + SignatureAlgorithm: "RSA_SHA256", + XmlSignKeyInfoKeyNameTransformer: "NONE", + PostBindingAuthnRequest: keycloak.KeycloakBoolQuoted(!firstPostBindingRequest), + PostBindingResponse: keycloak.KeycloakBoolQuoted(!firstPostBindingResponse), + PostBindingLogout: keycloak.KeycloakBoolQuoted(!firstPostBindingLogout), + ForceAuthn: keycloak.KeycloakBoolQuoted(!firstForceAuthn), + WantAssertionsSigned: keycloak.KeycloakBoolQuoted(!firstAssertionsSigned), + WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(!firstAssertionsEncrypted), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlIdentityProviderDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakSamlIdentityProvider_basicFromInterface(firstSaml), + Check: testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), + }, + { + Config: testKeycloakSamlIdentityProvider_basicFromInterface(secondSaml), + Check: testAccCheckKeycloakSamlIdentityProviderExists("keycloak_saml_identity_provider.saml"), + }, + }, + }) +} + +func testAccCheckKeycloakSamlIdentityProviderExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakSamlIdentityProviderFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakSamlIdentityProviderFetch(resourceName string, saml *keycloak.IdentityProvider) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedSaml, err := getKeycloakSamlIdentityProviderFromState(s, resourceName) + if err != nil { + return err + } + + saml.Alias = fetchedSaml.Alias + saml.Realm = fetchedSaml.Realm + + return nil + } +} + +func testAccCheckKeycloakSamlIdentityProviderDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_saml_identity_provider" { + continue + } + + id := rs.Primary.ID + realm := rs.Primary.Attributes["realm"] + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + saml, _ := keycloakClient.GetIdentityProvider(realm, id) + if saml != nil { + return fmt.Errorf("saml config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakSamlIdentityProviderFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProvider, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["alias"] + + saml, err := keycloakClient.GetIdentityProvider(realm, alias) + if err != nil { + return nil, fmt.Errorf("error getting saml identity provider config with alias %s: %s", alias, err) + } + + return saml, nil +} + +func testKeycloakSamlIdentityProvider_basic(realm, saml string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + single_sign_on_service_url = "https://example.com/auth" +} + `, realm, saml) +} + +func testKeycloakSamlIdentityProvider_basicFromInterface(saml *keycloak.IdentityProvider) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + enabled = %t + single_sign_on_service_url = "%s" + backchannel_supported = %t + validate_signature = %t + hide_on_login_page = %t + name_id_policy_format = "%s" + single_logout_service_url = "%s" + signing_certificate = "%s" + signature_algorithm = "%s", + xml_sign_key_info_key_name_transformer = "%s" + post_binding_authn_request = %t + post_binding_response = %t + post_binding_logout = %t + force_authn = %t + want_assertions_signed = %t + want_assertions_encrypted = %t +} + `, saml.Realm, saml.Alias, saml.Enabled, 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)) +} diff --git a/provider/keycloak_user_template_importer_identity_provider_mapper.go b/provider/keycloak_user_template_importer_identity_provider_mapper.go new file mode 100644 index 00000000..0248436b --- /dev/null +++ b/provider/keycloak_user_template_importer_identity_provider_mapper.go @@ -0,0 +1,43 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakUserTemplateImporterIdentityProviderMapper() *schema.Resource { + mapperSchema := map[string]*schema.Schema{ + "template": { + Type: schema.TypeString, + Optional: true, + Description: "Username For Template Import", + }, + } + genericMapperResource := resourceKeycloakIdentityProviderMapper() + genericMapperResource.Schema = mergeSchemas(genericMapperResource.Schema, mapperSchema) + genericMapperResource.Create = resourceKeycloakIdentityProviderMapperCreate(getUserTemplateImporterIdentityProviderMapperFromData, setUserTemplateImporterIdentityProviderMapperData) + genericMapperResource.Read = resourceKeycloakIdentityProviderMapperRead(setUserTemplateImporterIdentityProviderMapperData) + genericMapperResource.Update = resourceKeycloakIdentityProviderMapperUpdate(getUserTemplateImporterIdentityProviderMapperFromData, setUserTemplateImporterIdentityProviderMapperData) + return genericMapperResource +} + +func getUserTemplateImporterIdentityProviderMapperFromData(data *schema.ResourceData, meta interface{}) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := meta.(*keycloak.KeycloakClient) + rec, _ := getIdentityProviderMapperFromData(data) + identityProvider, err := keycloakClient.GetIdentityProvider(rec.Realm, rec.IdentityProviderAlias) + if err != nil { + return nil, handleNotFoundError(err, data) + } + rec.IdentityProviderMapper = fmt.Sprintf("%s-username-idp-mapper", identityProvider.ProviderId) + rec.Config = &keycloak.IdentityProviderMapperConfig{ + 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) + return nil +} diff --git a/provider/keycloak_user_template_importer_identity_provider_mapper_test.go b/provider/keycloak_user_template_importer_identity_provider_mapper_test.go new file mode 100644 index 00000000..1a925c41 --- /dev/null +++ b/provider/keycloak_user_template_importer_identity_provider_mapper_test.go @@ -0,0 +1,245 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" + "testing" +) + +func TestAccKeycloakUserTemplateIdentityProviderMapper_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + template := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakUserTemplateIdentityProviderMapper_basic(realmName, alias, mapperName, template), + Check: testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakUserTemplateIdentityProviderMapper_createAfterManualDestroy(t *testing.T) { + var mapper = &keycloak.IdentityProviderMapper{} + + realmName := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + template := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakUserTemplateIdentityProviderMapper_basic(realmName, alias, mapperName, template), + Check: testAccCheckKeycloakUserTemplateIdentityProviderMapperFetch("keycloak_user_template_importer_identity_provider_mapper.oidc", mapper), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteIdentityProviderMapper(mapper.Realm, mapper.IdentityProviderAlias, mapper.Id) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakUserTemplateIdentityProviderMapper_basic(realmName, alias, mapperName, template), + Check: testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.oidc"), + }, + }, + }) +} + +func TestAccKeycloakUserTemplateIdentityProviderMapper_basicUpdateRealm(t *testing.T) { + firstRealm := "terraform-" + acctest.RandString(10) + secondRealm := "terraform-" + acctest.RandString(10) + mapperName := "terraform-" + acctest.RandString(10) + alias := "terraform-" + acctest.RandString(10) + template := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakUserTemplateIdentityProviderMapper_basic(firstRealm, alias, mapperName, template), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_user_template_importer_identity_provider_mapper.oidc", "realm", firstRealm), + ), + }, + { + Config: testKeycloakUserTemplateIdentityProviderMapper_basic(secondRealm, alias, mapperName, template), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.oidc"), + resource.TestCheckResourceAttr("keycloak_user_template_importer_identity_provider_mapper.oidc", "realm", secondRealm), + ), + }, + }, + }) +} + +func TestAccKeycloakUserTemplateIdentityProviderMapper_basicUpdateAll(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + identityProviderAliasName := "terraform-" + acctest.RandString(10) + + firstMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + Template: acctest.RandString(10), + }, + } + + secondMapper := &keycloak.IdentityProviderMapper{ + Realm: realmName, + IdentityProviderAlias: identityProviderAliasName, + Name: acctest.RandString(10), + Config: &keycloak.IdentityProviderMapperConfig{ + Template: acctest.RandString(10), + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakUserTemplateIdentityProviderMapper_basicFromInterface(firstMapper), + Check: testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.saml"), + }, + { + Config: testKeycloakUserTemplateIdentityProviderMapper_basicFromInterface(secondMapper), + Check: testAccCheckKeycloakUserTemplateIdentityProviderMapperExists("keycloak_user_template_importer_identity_provider_mapper.saml"), + }, + }, + }) +} + +func testAccCheckKeycloakUserTemplateIdentityProviderMapperExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getKeycloakUserTemplateIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakUserTemplateIdentityProviderMapperFetch(resourceName string, mapper *keycloak.IdentityProviderMapper) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedMapper, err := getKeycloakUserTemplateIdentityProviderMapperFromState(s, resourceName) + if err != nil { + return err + } + + mapper.IdentityProviderAlias = fetchedMapper.IdentityProviderAlias + mapper.Realm = fetchedMapper.Realm + mapper.Id = fetchedMapper.Id + + return nil + } +} + +func testAccCheckKeycloakUserTemplateIdentityProviderMapperDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_user_template_importer_identity_provider_mapper" { + continue + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + mapper, _ := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if mapper != nil { + return fmt.Errorf("oidc config with id %s still exists", id) + } + } + + return nil + } +} + +func getKeycloakUserTemplateIdentityProviderMapperFromState(s *terraform.State, resourceName string) (*keycloak.IdentityProviderMapper, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + realm := rs.Primary.Attributes["realm"] + alias := rs.Primary.Attributes["identity_provider_alias"] + id := rs.Primary.ID + + mapper, err := keycloakClient.GetIdentityProviderMapper(realm, alias, id) + if err != nil { + return nil, fmt.Errorf("error getting identity provider mapper config with id %s: %s", id, err) + } + + return mapper, nil +} + +func testKeycloakUserTemplateIdentityProviderMapper_basic(realm, alias, name, template string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_oidc_identity_provider" "oidc" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + authorization_url = "https://example.com/auth" + token_url = "https://example.com/token" + client_id = "example_id" + client_secret = "example_token" +} + +resource keycloak_user_template_importer_identity_provider_mapper oidc { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_oidc_identity_provider.oidc.alias}" + template = "%s" +} + `, realm, alias, name, template) +} + +func testKeycloakUserTemplateIdentityProviderMapper_basicFromInterface(mapper *keycloak.IdentityProviderMapper) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_identity_provider" "saml" { + realm = "${keycloak_realm.realm.id}" + alias = "%s" + single_sign_on_service_url = "https://example.com/auth" +} + +resource keycloak_user_template_importer_identity_provider_mapper saml { + realm = "${keycloak_realm.realm.id}" + name = "%s" + identity_provider_alias = "${keycloak_saml_identity_provider.saml.alias}" + template = "%s" +} + `, mapper.Realm, mapper.IdentityProviderAlias, mapper.Name, mapper.Config.Template) +} diff --git a/provider/provider.go b/provider/provider.go index 823d8421..f5b0be72 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -8,29 +8,36 @@ import ( func KeycloakProvider() *schema.Provider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ - "keycloak_realm": resourceKeycloakRealm(), - "keycloak_group": resourceKeycloakGroup(), - "keycloak_group_memberships": resourceKeycloakGroupMemberships(), - "keycloak_user": resourceKeycloakUser(), - "keycloak_openid_client": resourceKeycloakOpenidClient(), - "keycloak_openid_client_scope": resourceKeycloakOpenidClientScope(), - "keycloak_ldap_user_federation": resourceKeycloakLdapUserFederation(), - "keycloak_ldap_user_attribute_mapper": resourceKeycloakLdapUserAttributeMapper(), - "keycloak_ldap_group_mapper": resourceKeycloakLdapGroupMapper(), - "keycloak_ldap_msad_user_account_control_mapper": resourceKeycloakLdapMsadUserAccountControlMapper(), - "keycloak_ldap_full_name_mapper": resourceKeycloakLdapFullNameMapper(), - "keycloak_custom_user_federation": resourceKeycloakCustomUserFederation(), - "keycloak_openid_user_attribute_protocol_mapper": resourceKeycloakOpenIdUserAttributeProtocolMapper(), - "keycloak_openid_user_property_protocol_mapper": resourceKeycloakOpenIdUserPropertyProtocolMapper(), - "keycloak_openid_group_membership_protocol_mapper": resourceKeycloakOpenIdGroupMembershipProtocolMapper(), - "keycloak_openid_full_name_protocol_mapper": resourceKeycloakOpenIdFullNameProtocolMapper(), - "keycloak_openid_hardcoded_claim_protocol_mapper": resourceKeycloakOpenIdHardcodedClaimProtocolMapper(), - "keycloak_openid_audience_protocol_mapper": resourceKeycloakOpenIdAudienceProtocolMapper(), - "keycloak_openid_client_default_scopes": resourceKeycloakOpenidClientDefaultScopes(), - "keycloak_openid_client_optional_scopes": resourceKeycloakOpenidClientOptionalScopes(), - "keycloak_saml_client": resourceKeycloakSamlClient(), - "keycloak_saml_user_attribute_protocol_mapper": resourceKeycloakSamlUserAttributeProtocolMapper(), - "keycloak_saml_user_property_protocol_mapper": resourceKeycloakSamlUserPropertyProtocolMapper(), + "keycloak_realm": resourceKeycloakRealm(), + "keycloak_group": resourceKeycloakGroup(), + "keycloak_group_memberships": resourceKeycloakGroupMemberships(), + "keycloak_user": resourceKeycloakUser(), + "keycloak_openid_client": resourceKeycloakOpenidClient(), + "keycloak_openid_client_scope": resourceKeycloakOpenidClientScope(), + "keycloak_ldap_user_federation": resourceKeycloakLdapUserFederation(), + "keycloak_ldap_user_attribute_mapper": resourceKeycloakLdapUserAttributeMapper(), + "keycloak_ldap_group_mapper": resourceKeycloakLdapGroupMapper(), + "keycloak_ldap_msad_user_account_control_mapper": resourceKeycloakLdapMsadUserAccountControlMapper(), + "keycloak_ldap_full_name_mapper": resourceKeycloakLdapFullNameMapper(), + "keycloak_custom_user_federation": resourceKeycloakCustomUserFederation(), + "keycloak_openid_user_attribute_protocol_mapper": resourceKeycloakOpenIdUserAttributeProtocolMapper(), + "keycloak_openid_user_property_protocol_mapper": resourceKeycloakOpenIdUserPropertyProtocolMapper(), + "keycloak_openid_group_membership_protocol_mapper": resourceKeycloakOpenIdGroupMembershipProtocolMapper(), + "keycloak_openid_full_name_protocol_mapper": resourceKeycloakOpenIdFullNameProtocolMapper(), + "keycloak_openid_hardcoded_claim_protocol_mapper": resourceKeycloakOpenIdHardcodedClaimProtocolMapper(), + "keycloak_openid_audience_protocol_mapper": resourceKeycloakOpenIdAudienceProtocolMapper(), + "keycloak_openid_client_default_scopes": resourceKeycloakOpenidClientDefaultScopes(), + "keycloak_openid_client_optional_scopes": resourceKeycloakOpenidClientOptionalScopes(), + "keycloak_saml_client": resourceKeycloakSamlClient(), + "keycloak_saml_user_attribute_protocol_mapper": resourceKeycloakSamlUserAttributeProtocolMapper(), + "keycloak_saml_user_property_protocol_mapper": resourceKeycloakSamlUserPropertyProtocolMapper(), + "keycloak_hardcoded_attribute_identity_provider_mapper": resourceKeycloakHardcodedAttributeIdentityProviderMapper(), + "keycloak_hardcoded_role_identity_provider_mapper": resourceKeycloakHardcodedRoleIdentityProviderMapper(), + "keycloak_attribute_importer_identity_provider_mapper": resourceKeycloakAttributeImporterIdentityProviderMapper(), + "keycloak_attribute_to_role_identity_provider_mapper": resourceKeycloakAttributeToRoleIdentityProviderMapper(), + "keycloak_user_template_importer_identity_provider_mapper": resourceKeycloakUserTemplateImporterIdentityProviderMapper(), + "keycloak_saml_identity_provider": resourceKeycloakSamlIdentityProvider(), + "keycloak_oidc_identity_provider": resourceKeycloakOidcIdentityProvider(), }, Schema: map[string]*schema.Schema{ "client_id": { diff --git a/provider/utils.go b/provider/utils.go index 9a01f1b9..657f0b9f 100644 --- a/provider/utils.go +++ b/provider/utils.go @@ -7,6 +7,22 @@ import ( "time" ) +func keys(data map[string]string) []string { + var result = []string{} + for k := range data { + result = append(result, k) + } + return result +} + +func mergeSchemas(a map[string]*schema.Schema, b map[string]*schema.Schema) map[string]*schema.Schema { + result := a + for k, v := range b { + result[k] = v + } + return result +} + // Converts duration string to an int representing the number of seconds, which is used by the Keycloak API // Ex: "1h" => 3600 func getSecondsFromDurationString(s string) (int, error) {