diff --git a/internal/provider/structure_auth0_connection.go b/internal/auth0/connection/expand.go similarity index 55% rename from internal/provider/structure_auth0_connection.go rename to internal/auth0/connection/expand.go index 7580a34e6..08b6c1f1e 100644 --- a/internal/provider/structure_auth0_connection.go +++ b/internal/auth0/connection/expand.go @@ -1,4 +1,4 @@ -package provider +package connection import ( "fmt" @@ -7,607 +7,10 @@ import ( "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/auth0/terraform-provider-auth0/internal/value" ) -func flattenConnectionOptions(d *schema.ResourceData, options interface{}) ([]interface{}, diag.Diagnostics) { - if options == nil { - return nil, nil - } - - var m interface{} - var diags diag.Diagnostics - switch connectionOptions := options.(type) { - case *management.ConnectionOptions: - m, diags = flattenConnectionOptionsAuth0(d, connectionOptions) - case *management.ConnectionOptionsGoogleOAuth2: - m, diags = flattenConnectionOptionsGoogleOAuth2(connectionOptions) - case *management.ConnectionOptionsGoogleApps: - m, diags = flattenConnectionOptionsGoogleApps(connectionOptions) - case *management.ConnectionOptionsOAuth2: - m, diags = flattenConnectionOptionsOAuth2(connectionOptions) - case *management.ConnectionOptionsFacebook: - m, diags = flattenConnectionOptionsFacebook(connectionOptions) - case *management.ConnectionOptionsApple: - m, diags = flattenConnectionOptionsApple(connectionOptions) - case *management.ConnectionOptionsLinkedin: - m, diags = flattenConnectionOptionsLinkedin(connectionOptions) - case *management.ConnectionOptionsGitHub: - m, diags = flattenConnectionOptionsGitHub(connectionOptions) - case *management.ConnectionOptionsWindowsLive: - m, diags = flattenConnectionOptionsWindowsLive(connectionOptions) - case *management.ConnectionOptionsSalesforce: - m, diags = flattenConnectionOptionsSalesforce(connectionOptions) - case *management.ConnectionOptionsEmail: - m, diags = flattenConnectionOptionsEmail(connectionOptions) - case *management.ConnectionOptionsSMS: - m, diags = flattenConnectionOptionsSMS(connectionOptions) - case *management.ConnectionOptionsOIDC: - m, diags = flattenConnectionOptionsOIDC(connectionOptions) - case *management.ConnectionOptionsOkta: - m, diags = flattenConnectionOptionsOkta(connectionOptions) - case *management.ConnectionOptionsAD: - m, diags = flattenConnectionOptionsAD(connectionOptions) - case *management.ConnectionOptionsAzureAD: - m, diags = flattenConnectionOptionsAzureAD(connectionOptions) - case *management.ConnectionOptionsADFS: - m, diags = flattenConnectionOptionsADFS(connectionOptions) - case *management.ConnectionOptionsSAML: - m, diags = flattenConnectionOptionsSAML(d, connectionOptions) - } - - return []interface{}{m}, diags -} - -func flattenConnectionOptionsGitHub(options *management.ConnectionOptionsGitHub) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "scopes": options.Scopes(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsWindowsLive(options *management.ConnectionOptionsWindowsLive) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "strategy_version": options.GetStrategyVersion(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsAuth0( - d *schema.ResourceData, - options *management.ConnectionOptions, -) (interface{}, diag.Diagnostics) { - dbSecretConfig, ok := d.GetOk("options.0.configuration") - if !ok { - dbSecretConfig = make(map[string]interface{}) - } - - m := map[string]interface{}{ - "password_policy": options.GetPasswordPolicy(), - "enable_script_context": options.GetEnableScriptContext(), - "enabled_database_customization": options.GetEnabledDatabaseCustomization(), - "brute_force_protection": options.GetBruteForceProtection(), - "import_mode": options.GetImportMode(), - "disable_signup": options.GetDisableSignup(), - "requires_username": options.GetRequiresUsername(), - "custom_scripts": options.GetCustomScripts(), - "configuration": dbSecretConfig, // Values do not get read back. - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "set_user_root_attributes": options.GetSetUserAttributes(), - } - - if options.PasswordComplexityOptions != nil { - m["password_complexity_options"] = []interface{}{options.PasswordComplexityOptions} - } - if options.PasswordDictionary != nil { - m["password_dictionary"] = []interface{}{options.PasswordDictionary} - } - if options.PasswordNoPersonalInfo != nil { - m["password_no_personal_info"] = []interface{}{options.PasswordNoPersonalInfo} - } - if options.PasswordHistory != nil { - m["password_history"] = []interface{}{options.PasswordHistory} - } - if options.MFA != nil { - m["mfa"] = []interface{}{options.MFA} - } - if options.Validation != nil { - m["validation"] = []interface{}{ - map[string]interface{}{ - "username": []interface{}{ - options.Validation["username"], - }, - }, - } - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - diags := checkForUnmanagedConfigurationSecrets( - dbSecretConfig.(map[string]interface{}), - options.GetConfiguration(), - ) - - return m, diags -} - -// checkForUnmanagedConfigurationSecrets is used to assess keys diff because values are sent back encrypted. -func checkForUnmanagedConfigurationSecrets(configFromTF map[string]interface{}, configFromAPI map[string]string) diag.Diagnostics { - var warnings diag.Diagnostics - - for key := range configFromAPI { - if _, ok := configFromTF[key]; !ok { - warnings = append(warnings, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Unmanaged Configuration Secret", - Detail: fmt.Sprintf("Detected a configuration secret not managed though terraform: %q. "+ - "If you proceed, this configuration secret will get deleted. It is required to "+ - "add this configuration secret to your custom database settings to "+ - "prevent unintentionally destructive results.", - key, - ), - AttributePath: cty.Path{cty.GetAttrStep{Name: "options.configuration"}}, - }) - } - } - - return warnings -} - -func flattenConnectionOptionsGoogleOAuth2( - options *management.ConnectionOptionsGoogleOAuth2, -) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "allowed_audiences": options.GetAllowedAudiences(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsGoogleApps( - options *management.ConnectionOptionsGoogleApps, -) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "domain": options.GetDomain(), - "tenant_domain": options.GetTenantDomain(), - "api_enable_users": options.GetEnableUsersAPI(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "domain_aliases": options.GetDomainAliases(), - "icon_url": options.GetLogoURL(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsOAuth2(options *management.ConnectionOptionsOAuth2) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "scopes": options.Scopes(), - "token_endpoint": options.GetTokenURL(), - "authorization_endpoint": options.GetAuthorizationURL(), - "scripts": options.GetScripts(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "icon_url": options.GetLogoURL(), - "pkce_enabled": options.GetPKCEEnabled(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsFacebook(options *management.ConnectionOptionsFacebook) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsApple(options *management.ConnectionOptionsApple) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "team_id": options.GetTeamID(), - "key_id": options.GetKeyID(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsLinkedin(options *management.ConnectionOptionsLinkedin) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "strategy_version": options.GetStrategyVersion(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsSalesforce(options *management.ConnectionOptionsSalesforce) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "community_base_url": options.GetCommunityBaseURL(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsSMS(options *management.ConnectionOptionsSMS) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "name": options.GetName(), - "from": options.GetFrom(), - "syntax": options.GetSyntax(), - "template": options.GetTemplate(), - "twilio_sid": options.GetTwilioSID(), - "twilio_token": options.GetTwilioToken(), - "messaging_service_sid": options.GetMessagingServiceSID(), - "disable_signup": options.GetDisableSignup(), - "brute_force_protection": options.GetBruteForceProtection(), - "provider": options.GetProvider(), - "gateway_url": options.GetGatewayURL(), - "forward_request_info": options.GetForwardRequestInfo(), - } - - if options.OTP != nil { - m["totp"] = []interface{}{ - map[string]interface{}{ - "time_step": options.OTP.GetTimeStep(), - "length": options.OTP.GetLength(), - }, - } - } - - if options.GatewayAuthentication != nil { - m["gateway_authentication"] = []interface{}{ - map[string]interface{}{ - "method": options.GatewayAuthentication.GetMethod(), - "subject": options.GatewayAuthentication.GetSubject(), - "audience": options.GatewayAuthentication.GetAudience(), - "secret": options.GatewayAuthentication.GetSecret(), - "secret_base64_encoded": options.GatewayAuthentication.GetSecretBase64Encoded(), - }, - } - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsOIDC(options *management.ConnectionOptionsOIDC) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "icon_url": options.GetLogoURL(), - "tenant_domain": options.GetTenantDomain(), - "domain_aliases": options.GetDomainAliases(), - "type": options.GetType(), - "scopes": options.Scopes(), - "issuer": options.GetIssuer(), - "jwks_uri": options.GetJWKSURI(), - "discovery_url": options.GetDiscoveryURL(), - "token_endpoint": options.GetTokenEndpoint(), - "userinfo_endpoint": options.GetUserInfoEndpoint(), - "authorization_endpoint": options.GetAuthorizationEndpoint(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsOkta(options *management.ConnectionOptionsOkta) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "domain": options.GetDomain(), - "domain_aliases": options.GetDomainAliases(), - "scopes": options.Scopes(), - "issuer": options.GetIssuer(), - "jwks_uri": options.GetJWKSURI(), - "token_endpoint": options.GetTokenEndpoint(), - "userinfo_endpoint": options.GetUserInfoEndpoint(), - "authorization_endpoint": options.GetAuthorizationEndpoint(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "icon_url": options.GetLogoURL(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsEmail(options *management.ConnectionOptionsEmail) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "name": options.GetName(), - "from": options.GetEmail().GetFrom(), - "syntax": options.GetEmail().GetSyntax(), - "subject": options.GetEmail().GetSubject(), - "template": options.GetEmail().GetBody(), - "disable_signup": options.GetDisableSignup(), - "brute_force_protection": options.GetBruteForceProtection(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - if options.OTP != nil { - m["totp"] = []interface{}{ - map[string]interface{}{ - "time_step": options.OTP.GetTimeStep(), - "length": options.OTP.GetLength(), - }, - } - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - if options.AuthParams != nil { - v, ok := options.AuthParams.(map[string]interface{}) - if !ok { - return m, diag.Diagnostics{{ - Severity: diag.Warning, - Summary: "Unable to cast auth_params to map[string]string", - Detail: fmt.Sprintf(`Authentication Parameters are required to be a map of strings, the existing value of %v is not compatible. It is recommended to express the existing value as a valid map[string]string. Subsequent terraform applys will clear this configuration to empty map.`, options.AuthParams), - AttributePath: cty.Path{cty.GetAttrStep{Name: "options.auth_params"}}, - }} - } - m["auth_params"] = v - } - - return m, nil -} - -func flattenConnectionOptionsAD(options *management.ConnectionOptionsAD) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "tenant_domain": options.GetTenantDomain(), - "domain_aliases": options.GetDomainAliases(), - "icon_url": options.GetLogoURL(), - "ips": options.GetIPs(), - "use_cert_auth": options.GetCertAuth(), - "use_kerberos": options.GetKerberos(), - "disable_cache": options.GetDisableCache(), - "brute_force_protection": options.GetBruteForceProtection(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsAzureAD(options *management.ConnectionOptionsAzureAD) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "client_id": options.GetClientID(), - "client_secret": options.GetClientSecret(), - "app_id": options.GetAppID(), - "tenant_domain": options.GetTenantDomain(), - "domain": options.GetDomain(), - "domain_aliases": options.GetDomainAliases(), - "icon_url": options.GetLogoURL(), - "identity_api": options.GetIdentityAPI(), - "waad_protocol": options.GetWAADProtocol(), - "waad_common_endpoint": options.GetUseCommonEndpoint(), - "use_wsfed": options.GetUseWSFederation(), - "api_enable_users": options.GetEnableUsersAPI(), - "max_groups_to_retrieve": options.GetMaxGroupsToRetrieve(), - "scopes": options.Scopes(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "should_trust_email_verified_connection": options.GetTrustEmailVerified(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsADFS(options *management.ConnectionOptionsADFS) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "tenant_domain": options.GetTenantDomain(), - "domain_aliases": options.GetDomainAliases(), - "icon_url": options.GetLogoURL(), - "adfs_server": options.GetADFSServer(), - "fed_metadata_xml": options.GetFedMetadataXML(), - "sign_in_endpoint": options.GetSignInEndpoint(), - "api_enable_users": options.GetEnableUsersAPI(), - "should_trust_email_verified_connection": options.GetTrustEmailVerified(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - } - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - -func flattenConnectionOptionsSAML( - d *schema.ResourceData, - options *management.ConnectionOptionsSAML, -) (interface{}, diag.Diagnostics) { - m := map[string]interface{}{ - "signing_cert": options.GetSigningCert(), - "protocol_binding": options.GetProtocolBinding(), - "debug": options.GetDebug(), - "tenant_domain": options.GetTenantDomain(), - "domain_aliases": options.GetDomainAliases(), - "sign_in_endpoint": options.GetSignInEndpoint(), - "sign_out_endpoint": options.GetSignOutEndpoint(), - "disable_sign_out": options.GetDisableSignOut(), - "signature_algorithm": options.GetSignatureAlgorithm(), - "digest_algorithm": options.GetDigestAglorithm(), - "sign_saml_request": options.GetSignSAMLRequest(), - "icon_url": options.GetLogoURL(), - "request_template": options.GetRequestTemplate(), - "user_id_attribute": options.GetUserIDAttribute(), - "set_user_root_attributes": options.GetSetUserAttributes(), - "non_persistent_attrs": options.GetNonPersistentAttrs(), - "entity_id": options.GetEntityID(), - "metadata_url": options.GetMetadataURL(), - "metadata_xml": d.Get("options.0.metadata_xml").(string), // Does not get read back. - } - - if options.IdpInitiated != nil { - m["idp_initiated"] = []interface{}{ - map[string]interface{}{ - "client_id": options.IdpInitiated.GetClientID(), - "client_protocol": options.IdpInitiated.GetClientProtocol(), - "client_authorize_query": options.IdpInitiated.GetClientAuthorizeQuery(), - }, - } - } - - if options.SigningKey != nil { - m["signing_key"] = []interface{}{ - map[string]interface{}{ - "key": options.SigningKey.GetKey(), - "cert": options.SigningKey.GetCert(), - }, - } - } - - fieldsMap, err := structure.FlattenJsonToString(options.FieldsMap) - if err != nil { - return nil, diag.FromErr(err) - } - m["fields_map"] = fieldsMap - - upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) - if err != nil { - return nil, diag.FromErr(err) - } - m["upstream_params"] = upstreamParams - - return m, nil -} - func expandConnection(d *schema.ResourceData) (*management.Connection, diag.Diagnostics) { config := d.GetRawConfig() diff --git a/internal/auth0/connection/flatten.go b/internal/auth0/connection/flatten.go new file mode 100644 index 000000000..8cb1a8f88 --- /dev/null +++ b/internal/auth0/connection/flatten.go @@ -0,0 +1,607 @@ +package connection + +import ( + "fmt" + + "github.com/auth0/go-auth0/management" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" +) + +func flattenConnectionOptions(d *schema.ResourceData, options interface{}) ([]interface{}, diag.Diagnostics) { + if options == nil { + return nil, nil + } + + var m interface{} + var diags diag.Diagnostics + switch connectionOptions := options.(type) { + case *management.ConnectionOptions: + m, diags = flattenConnectionOptionsAuth0(d, connectionOptions) + case *management.ConnectionOptionsGoogleOAuth2: + m, diags = flattenConnectionOptionsGoogleOAuth2(connectionOptions) + case *management.ConnectionOptionsGoogleApps: + m, diags = flattenConnectionOptionsGoogleApps(connectionOptions) + case *management.ConnectionOptionsOAuth2: + m, diags = flattenConnectionOptionsOAuth2(connectionOptions) + case *management.ConnectionOptionsFacebook: + m, diags = flattenConnectionOptionsFacebook(connectionOptions) + case *management.ConnectionOptionsApple: + m, diags = flattenConnectionOptionsApple(connectionOptions) + case *management.ConnectionOptionsLinkedin: + m, diags = flattenConnectionOptionsLinkedin(connectionOptions) + case *management.ConnectionOptionsGitHub: + m, diags = flattenConnectionOptionsGitHub(connectionOptions) + case *management.ConnectionOptionsWindowsLive: + m, diags = flattenConnectionOptionsWindowsLive(connectionOptions) + case *management.ConnectionOptionsSalesforce: + m, diags = flattenConnectionOptionsSalesforce(connectionOptions) + case *management.ConnectionOptionsEmail: + m, diags = flattenConnectionOptionsEmail(connectionOptions) + case *management.ConnectionOptionsSMS: + m, diags = flattenConnectionOptionsSMS(connectionOptions) + case *management.ConnectionOptionsOIDC: + m, diags = flattenConnectionOptionsOIDC(connectionOptions) + case *management.ConnectionOptionsOkta: + m, diags = flattenConnectionOptionsOkta(connectionOptions) + case *management.ConnectionOptionsAD: + m, diags = flattenConnectionOptionsAD(connectionOptions) + case *management.ConnectionOptionsAzureAD: + m, diags = flattenConnectionOptionsAzureAD(connectionOptions) + case *management.ConnectionOptionsADFS: + m, diags = flattenConnectionOptionsADFS(connectionOptions) + case *management.ConnectionOptionsSAML: + m, diags = flattenConnectionOptionsSAML(d, connectionOptions) + } + + return []interface{}{m}, diags +} + +func flattenConnectionOptionsGitHub(options *management.ConnectionOptionsGitHub) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "scopes": options.Scopes(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsWindowsLive(options *management.ConnectionOptionsWindowsLive) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "strategy_version": options.GetStrategyVersion(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsAuth0( + d *schema.ResourceData, + options *management.ConnectionOptions, +) (interface{}, diag.Diagnostics) { + dbSecretConfig, ok := d.GetOk("options.0.configuration") + if !ok { + dbSecretConfig = make(map[string]interface{}) + } + + m := map[string]interface{}{ + "password_policy": options.GetPasswordPolicy(), + "enable_script_context": options.GetEnableScriptContext(), + "enabled_database_customization": options.GetEnabledDatabaseCustomization(), + "brute_force_protection": options.GetBruteForceProtection(), + "import_mode": options.GetImportMode(), + "disable_signup": options.GetDisableSignup(), + "requires_username": options.GetRequiresUsername(), + "custom_scripts": options.GetCustomScripts(), + "configuration": dbSecretConfig, // Values do not get read back. + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "set_user_root_attributes": options.GetSetUserAttributes(), + } + + if options.PasswordComplexityOptions != nil { + m["password_complexity_options"] = []interface{}{options.PasswordComplexityOptions} + } + if options.PasswordDictionary != nil { + m["password_dictionary"] = []interface{}{options.PasswordDictionary} + } + if options.PasswordNoPersonalInfo != nil { + m["password_no_personal_info"] = []interface{}{options.PasswordNoPersonalInfo} + } + if options.PasswordHistory != nil { + m["password_history"] = []interface{}{options.PasswordHistory} + } + if options.MFA != nil { + m["mfa"] = []interface{}{options.MFA} + } + if options.Validation != nil { + m["validation"] = []interface{}{ + map[string]interface{}{ + "username": []interface{}{ + options.Validation["username"], + }, + }, + } + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + diags := checkForUnmanagedConfigurationSecrets( + dbSecretConfig.(map[string]interface{}), + options.GetConfiguration(), + ) + + return m, diags +} + +// checkForUnmanagedConfigurationSecrets is used to assess keys diff because values are sent back encrypted. +func checkForUnmanagedConfigurationSecrets(configFromTF map[string]interface{}, configFromAPI map[string]string) diag.Diagnostics { + var warnings diag.Diagnostics + + for key := range configFromAPI { + if _, ok := configFromTF[key]; !ok { + warnings = append(warnings, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unmanaged Configuration Secret", + Detail: fmt.Sprintf("Detected a configuration secret not managed though terraform: %q. "+ + "If you proceed, this configuration secret will get deleted. It is required to "+ + "add this configuration secret to your custom database settings to "+ + "prevent unintentionally destructive results.", + key, + ), + AttributePath: cty.Path{cty.GetAttrStep{Name: "options.configuration"}}, + }) + } + } + + return warnings +} + +func flattenConnectionOptionsGoogleOAuth2( + options *management.ConnectionOptionsGoogleOAuth2, +) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "allowed_audiences": options.GetAllowedAudiences(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsGoogleApps( + options *management.ConnectionOptionsGoogleApps, +) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "domain": options.GetDomain(), + "tenant_domain": options.GetTenantDomain(), + "api_enable_users": options.GetEnableUsersAPI(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "domain_aliases": options.GetDomainAliases(), + "icon_url": options.GetLogoURL(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsOAuth2(options *management.ConnectionOptionsOAuth2) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "scopes": options.Scopes(), + "token_endpoint": options.GetTokenURL(), + "authorization_endpoint": options.GetAuthorizationURL(), + "scripts": options.GetScripts(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "icon_url": options.GetLogoURL(), + "pkce_enabled": options.GetPKCEEnabled(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsFacebook(options *management.ConnectionOptionsFacebook) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsApple(options *management.ConnectionOptionsApple) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "team_id": options.GetTeamID(), + "key_id": options.GetKeyID(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsLinkedin(options *management.ConnectionOptionsLinkedin) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "strategy_version": options.GetStrategyVersion(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsSalesforce(options *management.ConnectionOptionsSalesforce) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "community_base_url": options.GetCommunityBaseURL(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsSMS(options *management.ConnectionOptionsSMS) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "name": options.GetName(), + "from": options.GetFrom(), + "syntax": options.GetSyntax(), + "template": options.GetTemplate(), + "twilio_sid": options.GetTwilioSID(), + "twilio_token": options.GetTwilioToken(), + "messaging_service_sid": options.GetMessagingServiceSID(), + "disable_signup": options.GetDisableSignup(), + "brute_force_protection": options.GetBruteForceProtection(), + "provider": options.GetProvider(), + "gateway_url": options.GetGatewayURL(), + "forward_request_info": options.GetForwardRequestInfo(), + } + + if options.OTP != nil { + m["totp"] = []interface{}{ + map[string]interface{}{ + "time_step": options.OTP.GetTimeStep(), + "length": options.OTP.GetLength(), + }, + } + } + + if options.GatewayAuthentication != nil { + m["gateway_authentication"] = []interface{}{ + map[string]interface{}{ + "method": options.GatewayAuthentication.GetMethod(), + "subject": options.GatewayAuthentication.GetSubject(), + "audience": options.GatewayAuthentication.GetAudience(), + "secret": options.GatewayAuthentication.GetSecret(), + "secret_base64_encoded": options.GatewayAuthentication.GetSecretBase64Encoded(), + }, + } + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsOIDC(options *management.ConnectionOptionsOIDC) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "icon_url": options.GetLogoURL(), + "tenant_domain": options.GetTenantDomain(), + "domain_aliases": options.GetDomainAliases(), + "type": options.GetType(), + "scopes": options.Scopes(), + "issuer": options.GetIssuer(), + "jwks_uri": options.GetJWKSURI(), + "discovery_url": options.GetDiscoveryURL(), + "token_endpoint": options.GetTokenEndpoint(), + "userinfo_endpoint": options.GetUserInfoEndpoint(), + "authorization_endpoint": options.GetAuthorizationEndpoint(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsOkta(options *management.ConnectionOptionsOkta) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "domain": options.GetDomain(), + "domain_aliases": options.GetDomainAliases(), + "scopes": options.Scopes(), + "issuer": options.GetIssuer(), + "jwks_uri": options.GetJWKSURI(), + "token_endpoint": options.GetTokenEndpoint(), + "userinfo_endpoint": options.GetUserInfoEndpoint(), + "authorization_endpoint": options.GetAuthorizationEndpoint(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "icon_url": options.GetLogoURL(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsEmail(options *management.ConnectionOptionsEmail) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "name": options.GetName(), + "from": options.GetEmail().GetFrom(), + "syntax": options.GetEmail().GetSyntax(), + "subject": options.GetEmail().GetSubject(), + "template": options.GetEmail().GetBody(), + "disable_signup": options.GetDisableSignup(), + "brute_force_protection": options.GetBruteForceProtection(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + if options.OTP != nil { + m["totp"] = []interface{}{ + map[string]interface{}{ + "time_step": options.OTP.GetTimeStep(), + "length": options.OTP.GetLength(), + }, + } + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + if options.AuthParams != nil { + v, ok := options.AuthParams.(map[string]interface{}) + if !ok { + return m, diag.Diagnostics{{ + Severity: diag.Warning, + Summary: "Unable to cast auth_params to map[string]string", + Detail: fmt.Sprintf(`Authentication Parameters are required to be a map of strings, the existing value of %v is not compatible. It is recommended to express the existing value as a valid map[string]string. Subsequent terraform applys will clear this configuration to empty map.`, options.AuthParams), + AttributePath: cty.Path{cty.GetAttrStep{Name: "options.auth_params"}}, + }} + } + m["auth_params"] = v + } + + return m, nil +} + +func flattenConnectionOptionsAD(options *management.ConnectionOptionsAD) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "tenant_domain": options.GetTenantDomain(), + "domain_aliases": options.GetDomainAliases(), + "icon_url": options.GetLogoURL(), + "ips": options.GetIPs(), + "use_cert_auth": options.GetCertAuth(), + "use_kerberos": options.GetKerberos(), + "disable_cache": options.GetDisableCache(), + "brute_force_protection": options.GetBruteForceProtection(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsAzureAD(options *management.ConnectionOptionsAzureAD) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "client_id": options.GetClientID(), + "client_secret": options.GetClientSecret(), + "app_id": options.GetAppID(), + "tenant_domain": options.GetTenantDomain(), + "domain": options.GetDomain(), + "domain_aliases": options.GetDomainAliases(), + "icon_url": options.GetLogoURL(), + "identity_api": options.GetIdentityAPI(), + "waad_protocol": options.GetWAADProtocol(), + "waad_common_endpoint": options.GetUseCommonEndpoint(), + "use_wsfed": options.GetUseWSFederation(), + "api_enable_users": options.GetEnableUsersAPI(), + "max_groups_to_retrieve": options.GetMaxGroupsToRetrieve(), + "scopes": options.Scopes(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "should_trust_email_verified_connection": options.GetTrustEmailVerified(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsADFS(options *management.ConnectionOptionsADFS) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "tenant_domain": options.GetTenantDomain(), + "domain_aliases": options.GetDomainAliases(), + "icon_url": options.GetLogoURL(), + "adfs_server": options.GetADFSServer(), + "fed_metadata_xml": options.GetFedMetadataXML(), + "sign_in_endpoint": options.GetSignInEndpoint(), + "api_enable_users": options.GetEnableUsersAPI(), + "should_trust_email_verified_connection": options.GetTrustEmailVerified(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + } + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} + +func flattenConnectionOptionsSAML( + d *schema.ResourceData, + options *management.ConnectionOptionsSAML, +) (interface{}, diag.Diagnostics) { + m := map[string]interface{}{ + "signing_cert": options.GetSigningCert(), + "protocol_binding": options.GetProtocolBinding(), + "debug": options.GetDebug(), + "tenant_domain": options.GetTenantDomain(), + "domain_aliases": options.GetDomainAliases(), + "sign_in_endpoint": options.GetSignInEndpoint(), + "sign_out_endpoint": options.GetSignOutEndpoint(), + "disable_sign_out": options.GetDisableSignOut(), + "signature_algorithm": options.GetSignatureAlgorithm(), + "digest_algorithm": options.GetDigestAglorithm(), + "sign_saml_request": options.GetSignSAMLRequest(), + "icon_url": options.GetLogoURL(), + "request_template": options.GetRequestTemplate(), + "user_id_attribute": options.GetUserIDAttribute(), + "set_user_root_attributes": options.GetSetUserAttributes(), + "non_persistent_attrs": options.GetNonPersistentAttrs(), + "entity_id": options.GetEntityID(), + "metadata_url": options.GetMetadataURL(), + "metadata_xml": d.Get("options.0.metadata_xml").(string), // Does not get read back. + } + + if options.IdpInitiated != nil { + m["idp_initiated"] = []interface{}{ + map[string]interface{}{ + "client_id": options.IdpInitiated.GetClientID(), + "client_protocol": options.IdpInitiated.GetClientProtocol(), + "client_authorize_query": options.IdpInitiated.GetClientAuthorizeQuery(), + }, + } + } + + if options.SigningKey != nil { + m["signing_key"] = []interface{}{ + map[string]interface{}{ + "key": options.SigningKey.GetKey(), + "cert": options.SigningKey.GetCert(), + }, + } + } + + fieldsMap, err := structure.FlattenJsonToString(options.FieldsMap) + if err != nil { + return nil, diag.FromErr(err) + } + m["fields_map"] = fieldsMap + + upstreamParams, err := structure.FlattenJsonToString(options.UpstreamParams) + if err != nil { + return nil, diag.FromErr(err) + } + m["upstream_params"] = upstreamParams + + return m, nil +} diff --git a/internal/provider/structure_auth0_connection_test.go b/internal/auth0/connection/flatten_test.go similarity index 99% rename from internal/provider/structure_auth0_connection_test.go rename to internal/auth0/connection/flatten_test.go index d09b037e7..740f00a5a 100644 --- a/internal/provider/structure_auth0_connection_test.go +++ b/internal/auth0/connection/flatten_test.go @@ -1,4 +1,4 @@ -package provider +package connection import ( "testing" diff --git a/internal/auth0/connection/resource.go b/internal/auth0/connection/resource.go new file mode 100644 index 000000000..2f36cb8a8 --- /dev/null +++ b/internal/auth0/connection/resource.go @@ -0,0 +1,134 @@ +package connection + +import ( + "context" + "net/http" + + "github.com/auth0/go-auth0/management" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// NewResource will return a new auth0_connection resource. +func NewResource() *schema.Resource { + return &schema.Resource{ + CreateContext: createConnection, + ReadContext: readConnection, + UpdateContext: updateConnection, + DeleteContext: deleteConnection, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Description: "With Auth0, you can define sources of users, otherwise known as connections, " + + "which may include identity providers (such as Google or LinkedIn), databases, or " + + "passwordless authentication methods. This resource allows you to configure " + + "and manage connections to be used with your clients and users.", + Schema: resourceSchema, + SchemaVersion: 2, + StateUpgraders: []schema.StateUpgrader{ + { + Type: connectionSchemaV0().CoreConfigSchema().ImpliedType(), + Upgrade: connectionSchemaUpgradeV0, + Version: 0, + }, + { + Type: connectionSchemaV1().CoreConfigSchema().ImpliedType(), + Upgrade: connectionSchemaUpgradeV1, + Version: 1, + }, + }, + } +} + +func createConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*management.Management) + + connection, diagnostics := expandConnection(d) + if diagnostics.HasError() { + return diagnostics + } + + if err := api.Connection.Create(connection); err != nil { + diagnostics = append(diagnostics, diag.FromErr(err)...) + return diagnostics + } + + d.SetId(connection.GetID()) + + diagnostics = append(diagnostics, readConnection(ctx, d, m)...) + return diagnostics +} + +func readConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*management.Management) + + connection, err := api.Connection.Read(d.Id()) + if err != nil { + if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + connectionOptions, diags := flattenConnectionOptions(d, connection.Options) + if diags.HasError() { + return diags + } + + result := multierror.Append( + d.Set("name", connection.GetName()), + d.Set("display_name", connection.GetDisplayName()), + d.Set("is_domain_connection", connection.GetIsDomainConnection()), + d.Set("strategy", connection.GetStrategy()), + d.Set("options", connectionOptions), + d.Set("realms", connection.GetRealms()), + d.Set("metadata", connection.GetMetadata()), + ) + + switch connection.GetStrategy() { + case management.ConnectionStrategyGoogleApps, + management.ConnectionStrategyOIDC, + management.ConnectionStrategyAD, + management.ConnectionStrategyAzureAD, + management.ConnectionStrategySAML, + management.ConnectionStrategyADFS: + result = multierror.Append(result, d.Set("show_as_button", connection.GetShowAsButton())) + } + + diags = append(diags, diag.FromErr(result.ErrorOrNil())...) + return diags +} + +func updateConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*management.Management) + + connection, diagnostics := expandConnection(d) + if diagnostics.HasError() { + return diagnostics + } + + if err := api.Connection.Update(d.Id(), connection); err != nil { + diagnostics = append(diagnostics, diag.FromErr(err)...) + return diagnostics + } + + diagnostics = append(diagnostics, readConnection(ctx, d, m)...) + return diagnostics +} + +func deleteConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*management.Management) + + if err := api.Connection.Delete(d.Id()); err != nil { + if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + d.SetId("") + return nil +} diff --git a/internal/provider/resource_auth0_connection_client.go b/internal/auth0/connection/resource_client.go similarity index 82% rename from internal/provider/resource_auth0_connection_client.go rename to internal/auth0/connection/resource_client.go index 0f7b719c3..f5fa6ab8e 100644 --- a/internal/provider/resource_auth0_connection_client.go +++ b/internal/auth0/connection/resource_client.go @@ -1,16 +1,17 @@ -package provider +package connection import ( "context" "fmt" "net/http" - "strings" "github.com/auth0/go-auth0/management" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/auth0/terraform-provider-auth0/internal/mutex" ) var ( @@ -18,7 +19,8 @@ var ( errInvalidConnectionClientIDFormat = fmt.Errorf("ID must be formated as :") ) -func newConnectionClient() *schema.Resource { +// NewClientResource will return a new auth0_connection_client resource. +func NewClientResource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ "connection_id": { @@ -54,42 +56,13 @@ func newConnectionClient() *schema.Resource { } } -func importConnectionClient( - _ context.Context, - data *schema.ResourceData, - _ interface{}, -) ([]*schema.ResourceData, error) { - rawID := data.Id() - if rawID == "" { - return nil, errEmptyConnectionClientID - } - - if !strings.Contains(rawID, ":") { - return nil, errInvalidConnectionClientIDFormat - } - - idPair := strings.Split(rawID, ":") - if len(idPair) != 2 { - return nil, errInvalidConnectionClientIDFormat - } - - result := multierror.Append( - data.Set("connection_id", idPair[0]), - data.Set("client_id", idPair[1]), - ) - - data.SetId(resource.UniqueId()) - - return []*schema.ResourceData{data}, result.ErrorOrNil() -} - func createConnectionClient(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { api := meta.(*management.Management) connectionID := data.Get("connection_id").(string) - globalMutex.Lock(connectionID) - defer globalMutex.Unlock(connectionID) + mutex.Global.Lock(connectionID) + defer mutex.Global.Unlock(connectionID) connection, err := api.Connection.Read(connectionID) if err != nil { @@ -150,8 +123,8 @@ func deleteConnectionClient(_ context.Context, data *schema.ResourceData, meta i connectionID := data.Get("connection_id").(string) - globalMutex.Lock(connectionID) - defer globalMutex.Unlock(connectionID) + mutex.Global.Lock(connectionID) + defer mutex.Global.Unlock(connectionID) connection, err := api.Connection.Read(connectionID) if err != nil { diff --git a/internal/auth0/connection/resource_client_import.go b/internal/auth0/connection/resource_client_import.go new file mode 100644 index 000000000..8ce0350c2 --- /dev/null +++ b/internal/auth0/connection/resource_client_import.go @@ -0,0 +1,39 @@ +package connection + +import ( + "context" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func importConnectionClient( + _ context.Context, + data *schema.ResourceData, + _ interface{}, +) ([]*schema.ResourceData, error) { + rawID := data.Id() + if rawID == "" { + return nil, errEmptyConnectionClientID + } + + if !strings.Contains(rawID, ":") { + return nil, errInvalidConnectionClientIDFormat + } + + idPair := strings.Split(rawID, ":") + if len(idPair) != 2 { + return nil, errInvalidConnectionClientIDFormat + } + + result := multierror.Append( + data.Set("connection_id", idPair[0]), + data.Set("client_id", idPair[1]), + ) + + data.SetId(resource.UniqueId()) + + return []*schema.ResourceData{data}, result.ErrorOrNil() +} diff --git a/internal/auth0/connection/resource_client_import_test.go b/internal/auth0/connection/resource_client_import_test.go new file mode 100644 index 000000000..d8a9e65f8 --- /dev/null +++ b/internal/auth0/connection/resource_client_import_test.go @@ -0,0 +1,61 @@ +package connection + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" +) + +func TestImportConnectionClient(t *testing.T) { + var testCases = []struct { + testName string + givenID string + expectedConnectionID string + expectedClientID string + expectedError error + }{ + { + testName: "it correctly parses the resource ID", + givenID: "conn_5678:client_1234", + expectedConnectionID: "conn_5678", + expectedClientID: "client_1234", + }, + { + testName: "it fails when the given ID is empty", + givenID: "", + expectedError: fmt.Errorf("ID cannot be empty"), + }, + { + testName: "it fails when the given ID does not have \":\" as a separator", + givenID: "client_1234conn_5678", + expectedError: fmt.Errorf("ID must be formated as :"), + }, + { + testName: "it fails when the given ID has too many separators", + givenID: "client_1234:conn_5678:", + expectedError: fmt.Errorf("ID must be formated as :"), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.testName, func(t *testing.T) { + data := schema.TestResourceDataRaw(t, NewClientResource().Schema, nil) + data.SetId(testCase.givenID) + + actualData, err := importConnectionClient(context.Background(), data, nil) + + if testCase.expectedError != nil { + assert.EqualError(t, err, testCase.expectedError.Error()) + assert.Nil(t, actualData) + return + } + + assert.Equal(t, actualData[0].Get("connection_id").(string), testCase.expectedConnectionID) + assert.Equal(t, actualData[0].Get("client_id").(string), testCase.expectedClientID) + assert.NotEqual(t, actualData[0].Id(), testCase.givenID) + }) + } +} diff --git a/internal/provider/resource_auth0_connection_client_test.go b/internal/auth0/connection/resource_client_test.go similarity index 69% rename from internal/provider/resource_auth0_connection_client_test.go rename to internal/auth0/connection/resource_client_test.go index 1883c7d3d..6e9dc64ac 100644 --- a/internal/provider/resource_auth0_connection_client_test.go +++ b/internal/auth0/connection/resource_client_test.go @@ -1,14 +1,12 @@ -package provider +package connection_test import ( - "context" "fmt" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/stretchr/testify/assert" + "github.com/auth0/terraform-provider-auth0/internal/provider" "github.com/auth0/terraform-provider-auth0/internal/recorder" "github.com/auth0/terraform-provider-auth0/internal/template" ) @@ -68,7 +66,7 @@ func TestAccConnectionClient(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccCreateConnectionClient, t.Name()), @@ -103,54 +101,3 @@ func TestAccConnectionClient(t *testing.T) { }, }) } - -func TestImportConnectionClient(t *testing.T) { - var testCases = []struct { - testName string - givenID string - expectedConnectionID string - expectedClientID string - expectedError error - }{ - { - testName: "it correctly parses the resource ID", - givenID: "conn_5678:client_1234", - expectedConnectionID: "conn_5678", - expectedClientID: "client_1234", - }, - { - testName: "it fails when the given ID is empty", - givenID: "", - expectedError: fmt.Errorf("ID cannot be empty"), - }, - { - testName: "it fails when the given ID does not have \":\" as a separator", - givenID: "client_1234conn_5678", - expectedError: fmt.Errorf("ID must be formated as :"), - }, - { - testName: "it fails when the given ID has too many separators", - givenID: "client_1234:conn_5678:", - expectedError: fmt.Errorf("ID must be formated as :"), - }, - } - - for _, testCase := range testCases { - t.Run(testCase.testName, func(t *testing.T) { - data := schema.TestResourceDataRaw(t, newConnectionClient().Schema, nil) - data.SetId(testCase.givenID) - - actualData, err := importConnectionClient(context.Background(), data, nil) - - if testCase.expectedError != nil { - assert.EqualError(t, err, testCase.expectedError.Error()) - assert.Nil(t, actualData) - return - } - - assert.Equal(t, actualData[0].Get("connection_id").(string), testCase.expectedConnectionID) - assert.Equal(t, actualData[0].Get("client_id").(string), testCase.expectedClientID) - assert.NotEqual(t, actualData[0].Id(), testCase.givenID) - }) - } -} diff --git a/internal/provider/resource_auth0_connection_test.go b/internal/auth0/connection/resource_test.go similarity index 95% rename from internal/provider/resource_auth0_connection_test.go rename to internal/auth0/connection/resource_test.go index 7a61108ec..b7be99648 100644 --- a/internal/provider/resource_auth0_connection_test.go +++ b/internal/auth0/connection/resource_test.go @@ -1,13 +1,12 @@ -package provider +package connection_test import ( - "context" "fmt" - "reflect" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/auth0/terraform-provider-auth0/internal/provider" "github.com/auth0/terraform-provider-auth0/internal/recorder" "github.com/auth0/terraform-provider-auth0/internal/sweep" "github.com/auth0/terraform-provider-auth0/internal/template" @@ -17,11 +16,17 @@ func init() { sweep.Connections() } +// This is needed so that the test +// sweepers get registered. +func TestMain(m *testing.M) { + resource.TestMain(m) +} + func TestAccConnection(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionConfig, t.Name()), @@ -169,7 +174,7 @@ func TestAccConnectionAD(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionADConfig, t.Name()), @@ -222,7 +227,7 @@ func TestAccConnectionAzureAD(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionAzureADConfig, t.Name()), @@ -290,7 +295,7 @@ func TestAccConnectionADFS(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionADFSConfig, t.Name()), @@ -426,7 +431,7 @@ func TestAccConnectionOIDC(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionOIDCConfig, t.Name()), @@ -543,7 +548,7 @@ func TestAccConnectionOkta(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionOktaConfig, t.Name()), @@ -657,7 +662,7 @@ func TestAccConnectionOAuth2(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionOAuth2Config, t.Name()), @@ -750,7 +755,7 @@ func TestAccConnectionSMS(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionSMSConfig, t.Name()), @@ -801,7 +806,7 @@ func TestAccConnectionCustomSMS(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionCustomSMSConfig, t.Name()), @@ -863,7 +868,7 @@ func TestAccConnectionEmail(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionEmailConfig, t.Name()), @@ -981,7 +986,7 @@ func TestAccConnectionSalesforce(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionSalesforceConfig, t.Name()), @@ -1018,7 +1023,7 @@ func TestAccConnectionGoogleOAuth2(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionGoogleOAuth2Config, t.Name()), @@ -1065,7 +1070,7 @@ func TestAccConnectionGoogleApps(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionGoogleApps, t.Name()), @@ -1118,7 +1123,7 @@ func TestAccConnectionFacebook(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionFacebookConfig, t.Name()), @@ -1185,7 +1190,7 @@ func TestAccConnectionApple(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionAppleConfig, t.Name()), @@ -1259,7 +1264,7 @@ func TestAccConnectionLinkedin(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionLinkedinConfig, t.Name()), @@ -1326,7 +1331,7 @@ func TestAccConnectionGitHub(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionGitHubConfig, t.Name()), @@ -1387,7 +1392,7 @@ func TestAccConnectionWindowslive(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionWindowsliveConfig, t.Name()), @@ -1457,7 +1462,7 @@ func TestAccConnectionConfiguration(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionConfigurationCreate, t.Name()), @@ -1511,127 +1516,11 @@ resource "auth0_connection" "my_connection" { } ` -func TestConnectionInstanceStateUpgradeV0(t *testing.T) { - for _, tt := range []struct { - name string - version interface{} - versionExpected int - }{ - { - name: "Empty", - version: "", - versionExpected: 0, - }, - { - name: "Zero", - version: "0", - versionExpected: 0, - }, - { - name: "NonZero", - version: "123", - versionExpected: 123, - }, - { - name: "Invalid", - version: "foo", - versionExpected: 0, - }, - } { - t.Run(tt.name, func(t *testing.T) { - state := map[string]interface{}{ - "options": []interface{}{ - map[string]interface{}{"strategy_version": tt.version}, - }, - } - - actual, err := connectionSchemaUpgradeV0(context.Background(), state, nil) - if err != nil { - t.Fatalf("error migrating state: %s", err) - } - - expected := map[string]interface{}{ - "options": []interface{}{ - map[string]interface{}{"strategy_version": tt.versionExpected}, - }, - } - - if !reflect.DeepEqual(expected, actual) { - t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual) - } - }) - } -} - -func TestConnectionInstanceStateUpgradeV1(t *testing.T) { - for _, tt := range []struct { - name string - validation map[string]string - validationExpected []map[string][]interface{} - }{ - { - name: "Only Min", - validation: map[string]string{ - "min": "5", - }, - validationExpected: []map[string][]interface{}{ - { - "username": []interface{}{ - map[string]string{ - "min": "5", - }, - }, - }, - }, - }, - { - name: "Min and Max", - validation: map[string]string{ - "min": "5", - "max": "10", - }, - validationExpected: []map[string][]interface{}{ - { - "username": []interface{}{ - map[string]string{ - "min": "5", - "max": "10", - }, - }, - }, - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - state := map[string]interface{}{ - "options": []interface{}{ - map[string]interface{}{"validation": tt.validation}, - }, - } - - actual, err := connectionSchemaUpgradeV1(context.Background(), state, nil) - if err != nil { - t.Fatalf("error migrating state: %s", err) - } - - expected := map[string]interface{}{ - "options": []interface{}{ - map[string]interface{}{"validation": tt.validationExpected}, - }, - } - - if !reflect.DeepEqual(expected, actual) { - t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual) - } - }) - } -} - func TestAccConnectionSAML(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testConnectionSAMLConfigCreate, t.Name()), @@ -1809,7 +1698,7 @@ func TestAccConnectionTwitter(t *testing.T) { httpRecorder := recorder.New(t) resource.Test(t, resource.TestCase{ - ProviderFactories: TestFactories(httpRecorder), + ProviderFactories: provider.TestFactories(httpRecorder), Steps: []resource.TestStep{ { Config: template.ParseTestName(testAccConnectionTwitterConfig, t.Name()), diff --git a/internal/provider/resource_auth0_connection.go b/internal/auth0/connection/schema.go similarity index 87% rename from internal/provider/resource_auth0_connection.go rename to internal/auth0/connection/schema.go index ddeea1225..986af0658 100644 --- a/internal/provider/resource_auth0_connection.go +++ b/internal/auth0/connection/schema.go @@ -1,19 +1,15 @@ -package provider +package connection import ( "context" "log" - "net/http" "strconv" - "github.com/auth0/go-auth0/management" - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -var connectionSchema = map[string]*schema.Schema{ +var resourceSchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, @@ -769,38 +765,8 @@ var connectionSchema = map[string]*schema.Schema{ }, } -func newConnection() *schema.Resource { - return &schema.Resource{ - CreateContext: createConnection, - ReadContext: readConnection, - UpdateContext: updateConnection, - DeleteContext: deleteConnection, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Description: "With Auth0, you can define sources of users, otherwise known as connections, " + - "which may include identity providers (such as Google or LinkedIn), databases, or " + - "passwordless authentication methods. This resource allows you to configure " + - "and manage connections to be used with your clients and users.", - Schema: connectionSchema, - SchemaVersion: 2, - StateUpgraders: []schema.StateUpgrader{ - { - Type: connectionSchemaV0().CoreConfigSchema().ImpliedType(), - Upgrade: connectionSchemaUpgradeV0, - Version: 0, - }, - { - Type: connectionSchemaV1().CoreConfigSchema().ImpliedType(), - Upgrade: connectionSchemaUpgradeV1, - Version: 1, - }, - }, - } -} - func connectionSchemaV0() *schema.Resource { - s := connectionSchema + s := resourceSchema s["strategy_version"] = &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -810,7 +776,7 @@ func connectionSchemaV0() *schema.Resource { } func connectionSchemaV1() *schema.Resource { - s := connectionSchema + s := resourceSchema s["validation"] = &schema.Schema{ Type: schema.TypeMap, Elem: &schema.Schema{Type: schema.TypeString}, @@ -890,95 +856,3 @@ func connectionSchemaUpgradeV1( return state, nil } - -func createConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - api := m.(*management.Management) - - connection, diagnostics := expandConnection(d) - if diagnostics.HasError() { - return diagnostics - } - - if err := api.Connection.Create(connection); err != nil { - diagnostics = append(diagnostics, diag.FromErr(err)...) - return diagnostics - } - - d.SetId(connection.GetID()) - - diagnostics = append(diagnostics, readConnection(ctx, d, m)...) - return diagnostics -} - -func readConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - api := m.(*management.Management) - - connection, err := api.Connection.Read(d.Id()) - if err != nil { - if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound { - d.SetId("") - return nil - } - return diag.FromErr(err) - } - - connectionOptions, diags := flattenConnectionOptions(d, connection.Options) - if diags.HasError() { - return diags - } - - result := multierror.Append( - d.Set("name", connection.GetName()), - d.Set("display_name", connection.GetDisplayName()), - d.Set("is_domain_connection", connection.GetIsDomainConnection()), - d.Set("strategy", connection.GetStrategy()), - d.Set("options", connectionOptions), - d.Set("realms", connection.GetRealms()), - d.Set("metadata", connection.GetMetadata()), - ) - - switch connection.GetStrategy() { - case management.ConnectionStrategyGoogleApps, - management.ConnectionStrategyOIDC, - management.ConnectionStrategyAD, - management.ConnectionStrategyAzureAD, - management.ConnectionStrategySAML, - management.ConnectionStrategyADFS: - result = multierror.Append(result, d.Set("show_as_button", connection.GetShowAsButton())) - } - - diags = append(diags, diag.FromErr(result.ErrorOrNil())...) - return diags -} - -func updateConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - api := m.(*management.Management) - - connection, diagnostics := expandConnection(d) - if diagnostics.HasError() { - return diagnostics - } - - if err := api.Connection.Update(d.Id(), connection); err != nil { - diagnostics = append(diagnostics, diag.FromErr(err)...) - return diagnostics - } - - diagnostics = append(diagnostics, readConnection(ctx, d, m)...) - return diagnostics -} - -func deleteConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - api := m.(*management.Management) - - if err := api.Connection.Delete(d.Id()); err != nil { - if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound { - d.SetId("") - return nil - } - return diag.FromErr(err) - } - - d.SetId("") - return nil -} diff --git a/internal/auth0/connection/schema_test.go b/internal/auth0/connection/schema_test.go new file mode 100644 index 000000000..307f428f6 --- /dev/null +++ b/internal/auth0/connection/schema_test.go @@ -0,0 +1,123 @@ +package connection + +import ( + "context" + "reflect" + "testing" +) + +func TestConnectionInstanceStateUpgradeV0(t *testing.T) { + for _, tt := range []struct { + name string + version interface{} + versionExpected int + }{ + { + name: "Empty", + version: "", + versionExpected: 0, + }, + { + name: "Zero", + version: "0", + versionExpected: 0, + }, + { + name: "NonZero", + version: "123", + versionExpected: 123, + }, + { + name: "Invalid", + version: "foo", + versionExpected: 0, + }, + } { + t.Run(tt.name, func(t *testing.T) { + state := map[string]interface{}{ + "options": []interface{}{ + map[string]interface{}{"strategy_version": tt.version}, + }, + } + + actual, err := connectionSchemaUpgradeV0(context.Background(), state, nil) + if err != nil { + t.Fatalf("error migrating state: %s", err) + } + + expected := map[string]interface{}{ + "options": []interface{}{ + map[string]interface{}{"strategy_version": tt.versionExpected}, + }, + } + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual) + } + }) + } +} + +func TestConnectionInstanceStateUpgradeV1(t *testing.T) { + for _, tt := range []struct { + name string + validation map[string]string + validationExpected []map[string][]interface{} + }{ + { + name: "Only Min", + validation: map[string]string{ + "min": "5", + }, + validationExpected: []map[string][]interface{}{ + { + "username": []interface{}{ + map[string]string{ + "min": "5", + }, + }, + }, + }, + }, + { + name: "Min and Max", + validation: map[string]string{ + "min": "5", + "max": "10", + }, + validationExpected: []map[string][]interface{}{ + { + "username": []interface{}{ + map[string]string{ + "min": "5", + "max": "10", + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + state := map[string]interface{}{ + "options": []interface{}{ + map[string]interface{}{"validation": tt.validation}, + }, + } + + actual, err := connectionSchemaUpgradeV1(context.Background(), state, nil) + if err != nil { + t.Fatalf("error migrating state: %s", err) + } + + expected := map[string]interface{}{ + "options": []interface{}{ + map[string]interface{}{"validation": tt.validationExpected}, + }, + } + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual) + } + }) + } +} diff --git a/internal/mutex/mutex.go b/internal/mutex/mutex.go index 99d504a0a..dd98f787b 100644 --- a/internal/mutex/mutex.go +++ b/internal/mutex/mutex.go @@ -5,6 +5,10 @@ import ( "sync" ) +// Global is instantiated just once and +// reused across different resources. +var Global = New() + // KeyValue is a simple key/value // store for arbitrary mutexes. type KeyValue struct { @@ -12,6 +16,13 @@ type KeyValue struct { store map[string]*sync.Mutex } +// New returns a properly initialized KeyValue mutex. +func New() *KeyValue { + return &KeyValue{ + store: make(map[string]*sync.Mutex), + } +} + // Lock the mutex for the given key. func (m *KeyValue) Lock(key string) { log.Printf("[DEBUG] Locking mutex for key: %q", key) @@ -41,10 +52,3 @@ func (m *KeyValue) get(key string) *sync.Mutex { return mutex } - -// New returns a properly initialized KeyValue mutex. -func New() *KeyValue { - return &KeyValue{ - store: make(map[string]*sync.Mutex), - } -} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9256d4031..07f6cc319 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,13 +12,11 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/meta" "github.com/auth0/terraform-provider-auth0/internal/auth0/client" - "github.com/auth0/terraform-provider-auth0/internal/mutex" + "github.com/auth0/terraform-provider-auth0/internal/auth0/connection" ) var version = "dev" -var globalMutex = mutex.New() - // New returns a *schema.Provider. func New() *schema.Provider { provider := &schema.Provider{ @@ -83,7 +81,8 @@ func New() *schema.Provider { "auth0_client": client.NewResource(), "auth0_global_client": client.NewGlobalResource(), "auth0_client_grant": newClientGrant(), - "auth0_connection": newConnection(), + "auth0_connection": connection.NewResource(), + "auth0_connection_client": connection.NewClientResource(), "auth0_custom_domain": newCustomDomain(), "auth0_custom_domain_verification": newCustomDomainVerification(), "auth0_resource_server": newResourceServer(), @@ -107,7 +106,6 @@ func New() *schema.Provider { "auth0_trigger_binding": newTriggerBinding(), "auth0_attack_protection": newAttackProtection(), "auth0_branding_theme": newBrandingTheme(), - "auth0_connection_client": newConnectionClient(), }, DataSourcesMap: map[string]*schema.Resource{ "auth0_client": client.NewDataSource(), diff --git a/internal/provider/resource_auth0_organization_connection.go b/internal/provider/resource_auth0_organization_connection.go index 78a49a077..448b0baf3 100644 --- a/internal/provider/resource_auth0_organization_connection.go +++ b/internal/provider/resource_auth0_organization_connection.go @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/auth0/terraform-provider-auth0/internal/mutex" ) var ( @@ -95,8 +97,8 @@ func createOrganizationConnection(ctx context.Context, data *schema.ResourceData organizationID := data.Get("organization_id").(string) - globalMutex.Lock(organizationID) - defer globalMutex.Unlock(organizationID) + mutex.Global.Lock(organizationID) + defer mutex.Global.Unlock(organizationID) connectionID := data.Get("connection_id").(string) assignMembershipOnLogin := data.Get("assign_membership_on_login").(bool) @@ -144,8 +146,8 @@ func updateOrganizationConnection(ctx context.Context, data *schema.ResourceData organizationID := data.Get("organization_id").(string) - globalMutex.Lock(organizationID) - defer globalMutex.Unlock(organizationID) + mutex.Global.Lock(organizationID) + defer mutex.Global.Unlock(organizationID) connectionID := data.Get("connection_id").(string) assignMembershipOnLogin := data.Get("assign_membership_on_login").(bool) @@ -166,8 +168,8 @@ func deleteOrganizationConnection(ctx context.Context, data *schema.ResourceData organizationID := data.Get("organization_id").(string) - globalMutex.Lock(organizationID) - defer globalMutex.Unlock(organizationID) + mutex.Global.Lock(organizationID) + defer mutex.Global.Unlock(organizationID) connectionID := data.Get("connection_id").(string) diff --git a/internal/provider/resource_auth0_organization_member.go b/internal/provider/resource_auth0_organization_member.go index e217b5841..cd56882a2 100644 --- a/internal/provider/resource_auth0_organization_member.go +++ b/internal/provider/resource_auth0_organization_member.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/auth0/terraform-provider-auth0/internal/mutex" "github.com/auth0/terraform-provider-auth0/internal/value" ) @@ -86,8 +87,8 @@ func createOrganizationMember(ctx context.Context, d *schema.ResourceData, m int userID := d.Get("user_id").(string) orgID := d.Get("organization_id").(string) - globalMutex.Lock(orgID) - defer globalMutex.Unlock(orgID) + mutex.Global.Lock(orgID) + defer mutex.Global.Unlock(orgID) if err := api.Organization.AddMembers(orgID, []string{userID}); err != nil { return diag.FromErr(err) @@ -184,8 +185,8 @@ func updateOrganizationMember(ctx context.Context, d *schema.ResourceData, m int orgID := d.Get("organization_id").(string) - globalMutex.Lock(orgID) - defer globalMutex.Unlock(orgID) + mutex.Global.Lock(orgID) + defer mutex.Global.Unlock(orgID) if err := assignRoles(d, api); err != nil { return diag.FromErr(fmt.Errorf("failed to assign members to organization. %w", err)) @@ -200,8 +201,8 @@ func deleteOrganizationMember(ctx context.Context, d *schema.ResourceData, m int userID := d.Get("user_id").(string) orgID := d.Get("organization_id").(string) - globalMutex.Lock(orgID) - defer globalMutex.Unlock(orgID) + mutex.Global.Lock(orgID) + defer mutex.Global.Unlock(orgID) if err := api.Organization.DeleteMember(orgID, []string{userID}); err != nil { if err, ok := err.(management.Error); ok && err.Status() == http.StatusNotFound {