From e600c0e18cbaab4374dd5070a9489f3a88a7de7d Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Wed, 21 Jun 2023 12:10:48 -0400 Subject: [PATCH] DXCDT-442: `auth0_connection_database` resource (#647) * Initial commit, tests passing for auth0 connection * Making strategy inferred, expanding entire connection now * types file into base * Removing notes * Renaming package name * Generated docs * Moving into existing connection package * Regenerating docs, additional renamings * Regenerating docs * Recording test * Cloning schema * Pulling-out description into parameter * Fixing flattening of configuration property * Fixing warning text * Renaming package name --------- Co-authored-by: Will Vedder --- docs/resources/connection_database.md | 187 +++++++ .../auth0_connection_database/import.sh | 4 + .../auth0_connection_database/resource.tf | 64 +++ internal/auth0/connection/base_connection.go | 199 +++++++ internal/auth0/connection/data_source.go | 4 +- .../auth0/connection/database/resource.go | 490 ++++++++++++++++++ .../connection/database/resource_test.go | 160 ++++++ internal/auth0/connection/flatten.go | 2 +- internal/auth0/connection/flatten_test.go | 2 +- internal/auth0/connection/resource.go | 24 +- internal/auth0/connection/schema.go | 70 ++- internal/provider/provider.go | 2 + .../recordings/TestAccConnectionDatabase.yaml | 326 ++++++++++++ 13 files changed, 1515 insertions(+), 19 deletions(-) create mode 100644 docs/resources/connection_database.md create mode 100644 examples/resources/auth0_connection_database/import.sh create mode 100644 examples/resources/auth0_connection_database/resource.tf create mode 100644 internal/auth0/connection/base_connection.go create mode 100644 internal/auth0/connection/database/resource.go create mode 100644 internal/auth0/connection/database/resource_test.go create mode 100644 test/data/recordings/TestAccConnectionDatabase.yaml diff --git a/docs/resources/connection_database.md b/docs/resources/connection_database.md new file mode 100644 index 000000000..b53b2f0a7 --- /dev/null +++ b/docs/resources/connection_database.md @@ -0,0 +1,187 @@ +--- +page_title: "Resource: auth0_connection_database" +description: |- + Auth0 provides database connections to authenticate users with an email/username and password. These credentials are securely stored in the Auth0 user store or in your own database. You can use this resource to create and manage database connections. +--- + +# Resource: auth0_connection_database + +Auth0 provides database connections to authenticate users with an email/username and password. These credentials are securely stored in the Auth0 user store or in your own database. You can use this resource to create and manage database connections. + +## Example Usage + +```terraform +# This is an example of an Auth0 connection. + +resource "auth0_connection_database" "my_connection" { + name = "Example-Connection" + is_domain_connection = true + strategy = "auth0" + metadata = { + key1 = "foo" + key2 = "bar" + } + + password_policy = "excellent" + brute_force_protection = true + enabled_database_customization = true + import_mode = false + requires_username = true + disable_signup = false + custom_scripts = { + get_user = < +## Schema + +### Required + +- `name` (String) Name of the connection. + +### Optional + +- `brute_force_protection` (Boolean) Indicates whether to enable brute force protection, which will limit the number of signups and failed logins from a suspicious IP address. +- `configuration` (Map of String, Sensitive) A case-sensitive map of key value pairs used as configuration variables for the `custom_script`. +- `custom_scripts` (Map of String) A map of scripts used to integrate with a custom database. +- `disable_self_service_change_password` (Boolean) Indicates whether to remove the forgot password link within the New Universal Login. +- `disable_signup` (Boolean) Indicates whether to allow user sign-ups to your application. +- `display_name` (String) Name used in login screen. +- `enable_script_context` (Boolean) Set to `true` to inject context into custom DB scripts (warning: cannot be disabled once enabled). +- `enabled_database_customization` (Boolean) Set to `true` to use a legacy user store. +- `import_mode` (Boolean) Indicates whether you have a legacy user store and want to gradually migrate those users to the Auth0 user store. +- `is_domain_connection` (Boolean) Indicates whether the connection is domain level. +- `metadata` (Map of String) Metadata associated with the connection, in the form of a map of string values (max 255 chars). Maximum of 10 metadata properties allowed. +- `mfa` (Block List, Max: 1) Configuration options for multifactor authentication. (see [below for nested schema](#nestedblock--mfa)) +- `non_persistent_attrs` (Set of String) If there are user fields that should not be stored in Auth0 databases due to privacy reasons, you can add them to the DenyList here. +- `password_complexity_options` (Block List, Max: 1) Configuration settings for password complexity. (see [below for nested schema](#nestedblock--password_complexity_options)) +- `password_dictionary` (Block List, Max: 1) Configuration settings for the password dictionary check, which does not allow passwords that are part of the password dictionary. (see [below for nested schema](#nestedblock--password_dictionary)) +- `password_history` (Block List) Configuration settings for the password history that is maintained for each user to prevent the reuse of passwords. (see [below for nested schema](#nestedblock--password_history)) +- `password_no_personal_info` (Block List, Max: 1) Configuration settings for the password personal info check, which does not allow passwords that contain any part of the user's personal data, including user's `name`, `username`, `nickname`, `user_metadata.name`, `user_metadata.first`, `user_metadata.last`, user's `email`, or first part of the user's `email`. (see [below for nested schema](#nestedblock--password_no_personal_info)) +- `password_policy` (String) Indicates level of password strength to enforce during authentication. A strong password policy will make it difficult, if not improbable, for someone to guess a password through either manual or automated means. Options include `none`, `low`, `fair`, `good`, `excellent`. +- `realms` (List of String) Defines the realms for which the connection will be used (e.g., email domains). If not specified, the connection name is added as the realm. +- `requires_username` (Boolean) Indicates whether the user is required to provide a username in addition to an email address. +- `set_user_root_attributes` (String) Determines whether to sync user profile attributes (`name`, `given_name`, `family_name`, `nickname`, `picture`) at each login or only on the first login. Options include: `on_each_login`, `on_first_login`. Default value: `on_each_login`. +- `show_as_button` (Boolean) Display connection as a button. Only available on enterprise connections. +- `upstream_params` (String) You can pass provider-specific parameters to an identity provider during authentication. The values can either be static per connection or dynamic per user. +- `validation` (Block List, Max: 1) Validation of the minimum and maximum values allowed for a user to have as username. (see [below for nested schema](#nestedblock--validation)) + +### Read-Only + +- `enabled_clients` (Set of String) IDs of the clients for which the connection is enabled. +- `id` (String) The ID of this resource. +- `strategy` (String) Type of the connection, which indicates the identity provider. + + +### Nested Schema for `mfa` + +Optional: + +- `active` (Boolean) Indicates whether multifactor authentication is enabled for this connection. +- `return_enroll_settings` (Boolean) Indicates whether multifactor authentication enrollment settings will be returned. + + + +### Nested Schema for `password_complexity_options` + +Optional: + +- `min_length` (Number) Minimum number of characters allowed in passwords. + + + +### Nested Schema for `password_dictionary` + +Optional: + +- `dictionary` (Set of String) Customized contents of the password dictionary. By default, the password dictionary contains a list of the [10,000 most common passwords](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt); your customized content is used in addition to the default password dictionary. Matching is not case-sensitive. +- `enable` (Boolean) Indicates whether the password dictionary check is enabled for this connection. + + + +### Nested Schema for `password_history` + +Optional: + +- `enable` (Boolean) +- `size` (Number) + + + +### Nested Schema for `password_no_personal_info` + +Optional: + +- `enable` (Boolean) + + + +### Nested Schema for `validation` + +Optional: + +- `username` (Block List, Max: 1) Specifies the `min` and `max` values of username length. (see [below for nested schema](#nestedblock--validation--username)) + + +### Nested Schema for `validation.username` + +Optional: + +- `max` (Number) +- `min` (Number) + +## Import + +Import is supported using the following syntax: + +```shell +# Connections can be imported using their ID. +# +# Example: +terraform import auth0_connection_database.my_connection con_a17f21fdb24d48a0 +``` diff --git a/examples/resources/auth0_connection_database/import.sh b/examples/resources/auth0_connection_database/import.sh new file mode 100644 index 000000000..053193302 --- /dev/null +++ b/examples/resources/auth0_connection_database/import.sh @@ -0,0 +1,4 @@ +# Connections can be imported using their ID. +# +# Example: +terraform import auth0_connection_database.my_connection con_a17f21fdb24d48a0 diff --git a/examples/resources/auth0_connection_database/resource.tf b/examples/resources/auth0_connection_database/resource.tf new file mode 100644 index 000000000..2acf25dde --- /dev/null +++ b/examples/resources/auth0_connection_database/resource.tf @@ -0,0 +1,64 @@ +# This is an example of an Auth0 connection. + +resource "auth0_connection_database" "my_connection" { + name = "Example-Connection" + is_domain_connection = true + strategy = "auth0" + metadata = { + key1 = "foo" + key2 = "bar" + } + + password_policy = "excellent" + brute_force_protection = true + enabled_database_customization = true + import_mode = false + requires_username = true + disable_signup = false + custom_scripts = { + get_user = < 0 { + validationOption["username"] = usernameValidation + } + + return stop + }, + ) + + if len(validationOption) > 0 { + options.Validation = validationOption + } + + return stop + }, + ) + + config.GetAttr("password_history").ForEachElement( + func(_ cty.Value, passwordHistory cty.Value) (stop bool) { + passwordHistoryOption := make(map[string]interface{}) + + if enable := value.Bool(passwordHistory.GetAttr("enable")); enable != nil { + passwordHistoryOption["enable"] = enable + } + + if size := value.Int(passwordHistory.GetAttr("size")); size != nil && *size != 0 { + passwordHistoryOption["size"] = size + } + + if len(passwordHistoryOption) > 0 { + options.PasswordHistory = passwordHistoryOption + } + + return stop + }, + ) + + config.GetAttr("password_no_personal_info").ForEachElement( + func(_ cty.Value, passwordNoPersonalInfo cty.Value) (stop bool) { + if enable := value.Bool(passwordNoPersonalInfo.GetAttr("enable")); enable != nil { + options.PasswordNoPersonalInfo = map[string]interface{}{ + "enable": enable, + } + } + + return stop + }, + ) + + config.GetAttr("password_dictionary").ForEachElement( + func(_ cty.Value, passwordDictionary cty.Value) (stop bool) { + passwordDictionaryOption := make(map[string]interface{}) + + if enable := value.Bool(passwordDictionary.GetAttr("enable")); enable != nil { + passwordDictionaryOption["enable"] = enable + } + if dictionary := value.Strings(passwordDictionary.GetAttr("dictionary")); dictionary != nil { + passwordDictionaryOption["dictionary"] = dictionary + } + + if len(passwordDictionaryOption) > 0 { + options.PasswordDictionary = passwordDictionaryOption + } + + return stop + }, + ) + + config.GetAttr("password_complexity_options").ForEachElement( + func(_ cty.Value, passwordComplexity cty.Value) (stop bool) { + if minLength := value.Int(passwordComplexity.GetAttr("min_length")); minLength != nil { + options.PasswordComplexityOptions = map[string]interface{}{ + "min_length": minLength, + } + } + + return stop + }, + ) + + config.GetAttr("mfa").ForEachElement( + func(_ cty.Value, mfa cty.Value) (stop bool) { + mfaOption := make(map[string]interface{}) + + if active := value.Bool(mfa.GetAttr("active")); active != nil { + mfaOption["active"] = active + } + if returnEnrollSettings := value.Bool(mfa.GetAttr("return_enroll_settings")); returnEnrollSettings != nil { + mfaOption["return_enroll_settings"] = returnEnrollSettings + } + + if len(mfaOption) > 0 { + options.MFA = mfaOption + } + + return stop + }, + ) + + var err error + options.UpstreamParams, err = value.MapFromJSON(config.GetAttr("upstream_params")) + if err != nil { + return nil, diag.FromErr(err) + } + + if !d.IsNewResource() { + apiConn, err := api.Connection.Read(d.Id()) + if err != nil { + return nil, diag.FromErr(err) + } + + diags := checkForUnmanagedConfigurationSecrets( + options.GetConfiguration(), + apiConn.Options.(*management.ConnectionOptions).GetConfiguration(), + ) + + if diags.HasError() { + return nil, diags + } + } else { + conn.Strategy = auth0.String("auth0") + } + + conn.Options = options + + return conn, nil +} + +// checkForUnmanagedConfigurationSecrets is used to assess keys diff because values are sent back encrypted. +func checkForUnmanagedConfigurationSecrets(configFromTF, 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 through 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: "configuration"}}, + }) + } + } + + return warnings +} diff --git a/internal/auth0/connection/database/resource_test.go b/internal/auth0/connection/database/resource_test.go new file mode 100644 index 000000000..29088f1fc --- /dev/null +++ b/internal/auth0/connection/database/resource_test.go @@ -0,0 +1,160 @@ +package database_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/auth0/terraform-provider-auth0/internal/acctest" +) + +func TestAccConnectionDatabase(t *testing.T) { + acctest.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: acctest.ParseTestName(testAccConnectionConfig, t.Name()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "name", fmt.Sprintf("Acceptance-Test-Connection-%s", t.Name())), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "is_domain_connection", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "strategy", "auth0"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "metadata.key1", "foo"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "metadata.key2", "bar"), + resource.TestCheckNoResourceAttr("auth0_connection_database.my_connection", "show_as_button"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "password_policy", "fair"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "password_no_personal_info.0.enable", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "password_dictionary.0.enable", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "password_complexity_options.0.min_length", "6"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "enabled_database_customization", "false"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "brute_force_protection", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "import_mode", "false"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "disable_signup", "false"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "disable_self_service_change_password", "false"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "requires_username", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "validation.0.username.0.min", "10"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "validation.0.username.0.max", "40"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "custom_scripts.get_user", "myFunction"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "mfa.0.active", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "mfa.0.return_enroll_settings", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "upstream_params", "{\"screen_name\":{\"alias\":\"login_hint\"}}"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "non_persistent_attrs.#", "2"), + resource.TestCheckTypeSetElemAttr("auth0_connection_database.my_connection", "non_persistent_attrs.*", "hair_color"), + resource.TestCheckTypeSetElemAttr("auth0_connection_database.my_connection", "non_persistent_attrs.*", "gender"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "configuration.foo", "bar"), + ), + }, + { + Config: acctest.ParseTestName(testAccConnectionConfigUpdate, t.Name()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "brute_force_protection", "false"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "mfa.0.return_enroll_settings", "false"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "upstream_params", ""), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "enable_script_context", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "enabled_database_customization", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "disable_self_service_change_password", "true"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "set_user_root_attributes", "on_first_login"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "non_persistent_attrs.#", "0"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "configuration.foo", "bar"), + resource.TestCheckResourceAttr("auth0_connection_database.my_connection", "configuration.bar", "baz"), + ), + }, + }, + }) +} + +const testAccConnectionConfig = ` +resource "auth0_connection_database" "my_connection" { + name = "Acceptance-Test-Connection-{{.testName}}" + is_domain_connection = true + + metadata = { + key1 = "foo" + key2 = "bar" + } + + password_policy = "fair" + password_history { + enable = true + size = 5 + } + password_no_personal_info { + enable = true + } + password_dictionary { + enable = true + dictionary = [ "password", "admin", "1234" ] + } + password_complexity_options { + min_length = 6 + } + validation { + username { + min = 10 + max = 40 + } + } + enabled_database_customization = false + brute_force_protection = true + import_mode = false + requires_username = true + disable_signup = false + disable_self_service_change_password = false + custom_scripts = { + get_user = "myFunction" + } + configuration = { + foo = "bar" + } + mfa { + active = true + return_enroll_settings = true + } + upstream_params = jsonencode({ + "screen_name": { + "alias": "login_hint" + } + }) + non_persistent_attrs = ["gender","hair_color"] +} +` + +const testAccConnectionConfigUpdate = ` +resource "auth0_connection_database" "my_connection" { + name = "Acceptance-Test-Connection-{{.testName}}" + is_domain_connection = true + + metadata = { + key1 = "foo" + key2 = "bar" + } + + password_policy = "fair" + password_history { + enable = true + size = 5 + } + password_no_personal_info { + enable = true + } + enable_script_context = true + enabled_database_customization = true + set_user_root_attributes = "on_first_login" + brute_force_protection = false + import_mode = false + disable_signup = false + disable_self_service_change_password = true + requires_username = true + custom_scripts = { + get_user = "myFunction" + } + configuration = { + foo = "bar" + bar = "baz" + } + mfa { + active = true + return_enroll_settings = false + } + non_persistent_attrs = [] +} +` diff --git a/internal/auth0/connection/flatten.go b/internal/auth0/connection/flatten.go index 00a87eee5..0a9e4444e 100644 --- a/internal/auth0/connection/flatten.go +++ b/internal/auth0/connection/flatten.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" ) -func flattenConnectionOptions(d *schema.ResourceData, options interface{}) ([]interface{}, diag.Diagnostics) { +func flattenConnectionOptionsV0(d *schema.ResourceData, options interface{}) ([]interface{}, diag.Diagnostics) { if options == nil { return nil, nil } diff --git a/internal/auth0/connection/flatten_test.go b/internal/auth0/connection/flatten_test.go index a70fa0414..faa62665c 100644 --- a/internal/auth0/connection/flatten_test.go +++ b/internal/auth0/connection/flatten_test.go @@ -8,7 +8,7 @@ import ( ) func TestFlattenConnectionOptions(t *testing.T) { - result, diags := flattenConnectionOptions(nil, nil) + result, diags := flattenConnectionOptionsV0(nil, nil) if diags != nil { t.Errorf("Expected nil diagnostics, got %v", diags) diff --git a/internal/auth0/connection/resource.go b/internal/auth0/connection/resource.go index bd64a5961..ceb961a0e 100644 --- a/internal/auth0/connection/resource.go +++ b/internal/auth0/connection/resource.go @@ -15,10 +15,10 @@ import ( // NewResource will return a new auth0_connection resource. func NewResource() *schema.Resource { return &schema.Resource{ - CreateContext: createConnection, - ReadContext: readConnection, - UpdateContext: updateConnection, - DeleteContext: deleteConnection, + CreateContext: createConnectionV0, + ReadContext: readConnectionV0, + UpdateContext: updateConnectionV0, + DeleteContext: deleteConnectionV0, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -26,7 +26,7 @@ func NewResource() *schema.Resource { "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, + Schema: resourceSchemaV0, SchemaVersion: 2, StateUpgraders: []schema.StateUpgrader{ { @@ -43,7 +43,7 @@ func NewResource() *schema.Resource { } } -func createConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func createConnectionV0(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := m.(*config.Config).GetAPI() connection, diagnostics := expandConnection(d, api) @@ -58,11 +58,11 @@ func createConnection(ctx context.Context, d *schema.ResourceData, m interface{} d.SetId(connection.GetID()) - diagnostics = append(diagnostics, readConnection(ctx, d, m)...) + diagnostics = append(diagnostics, readConnectionV0(ctx, d, m)...) return diagnostics } -func readConnection(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func readConnectionV0(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := m.(*config.Config).GetAPI() connection, err := api.Connection.Read(d.Id()) @@ -74,7 +74,7 @@ func readConnection(_ context.Context, d *schema.ResourceData, m interface{}) di return diag.FromErr(err) } - connectionOptions, diags := flattenConnectionOptions(d, connection.Options) + connectionOptions, diags := flattenConnectionOptionsV0(d, connection.Options) if diags.HasError() { return diags } @@ -104,7 +104,7 @@ func readConnection(_ context.Context, d *schema.ResourceData, m interface{}) di return diags } -func updateConnection(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func updateConnectionV0(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := m.(*config.Config).GetAPI() connection, diagnostics := expandConnection(d, api) @@ -117,11 +117,11 @@ func updateConnection(ctx context.Context, d *schema.ResourceData, m interface{} return diagnostics } - diagnostics = append(diagnostics, readConnection(ctx, d, m)...) + diagnostics = append(diagnostics, readConnectionV0(ctx, d, m)...) return diagnostics } -func deleteConnection(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func deleteConnectionV0(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := m.(*config.Config).GetAPI() if err := api.Connection.Delete(d.Id()); err != nil { diff --git a/internal/auth0/connection/schema.go b/internal/auth0/connection/schema.go index 833c50f3c..d7e5fa758 100644 --- a/internal/auth0/connection/schema.go +++ b/internal/auth0/connection/schema.go @@ -11,7 +11,7 @@ import ( internalSchema "github.com/auth0/terraform-provider-auth0/internal/schema" ) -var resourceSchema = map[string]*schema.Schema{ +var resourceSchemaV0 = map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, @@ -783,7 +783,7 @@ var resourceSchema = map[string]*schema.Schema{ } func connectionSchemaV0() *schema.Resource { - s := internalSchema.Clone(resourceSchema) + s := internalSchema.Clone(resourceSchemaV0) s["strategy_version"] = &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -793,7 +793,7 @@ func connectionSchemaV0() *schema.Resource { } func connectionSchemaV1() *schema.Resource { - s := internalSchema.Clone(resourceSchema) + s := internalSchema.Clone(resourceSchemaV0) s["validation"] = &schema.Schema{ Type: schema.TypeMap, Elem: &schema.Schema{Type: schema.TypeString}, @@ -873,3 +873,67 @@ func connectionSchemaUpgradeV1( return state, nil } + +var baseConnectionSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the connection.", + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: "Name used in login screen.", + }, + "is_domain_connection": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Indicates whether the connection is domain level.", + }, + "metadata": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ValidateDiagFunc: validation.MapKeyLenBetween(0, 10), + Description: "Metadata associated with the connection, in the form of a map of string values " + + "(max 255 chars). Maximum of 10 metadata properties allowed.", + }, + "realms": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Computed: true, + Description: "Defines the realms for which the connection will be used (e.g., email domains). " + + "If not specified, the connection name is added as the realm.", + }, + "show_as_button": { + Type: schema.TypeBool, + Optional: true, + Description: "Display connection as a button. Only available on enterprise connections.", + }, + "enabled_clients": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "IDs of the clients for which the connection is enabled.", + }, + "upstream_params": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsJSON, + Description: "You can pass provider-specific parameters to an identity provider during " + + "authentication. The values can either be static per connection or dynamic per user.", + }, + "non_persistent_attrs": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Computed: true, + Description: "If there are user fields that should not be stored in Auth0 databases due to " + + "privacy reasons, you can add them to the DenyList here.", + }, +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5195ddf4a..104db2dc5 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -10,6 +10,7 @@ import ( "github.com/auth0/terraform-provider-auth0/internal/auth0/branding" "github.com/auth0/terraform-provider-auth0/internal/auth0/client" "github.com/auth0/terraform-provider-auth0/internal/auth0/connection" + "github.com/auth0/terraform-provider-auth0/internal/auth0/connection/database" "github.com/auth0/terraform-provider-auth0/internal/auth0/customdomain" "github.com/auth0/terraform-provider-auth0/internal/auth0/email" "github.com/auth0/terraform-provider-auth0/internal/auth0/guardian" @@ -98,6 +99,7 @@ func New() *schema.Provider { "auth0_client_grant": client.NewGrantResource(), "auth0_global_client": client.NewGlobalResource(), "auth0_connection": connection.NewResource(), + "auth0_connection_database": database.NewConnectionResource(), "auth0_connection_client": connection.NewClientResource(), "auth0_connection_clients": connection.NewClientsResource(), "auth0_custom_domain": customdomain.NewResource(), diff --git a/test/data/recordings/TestAccConnectionDatabase.yaml b/test/data/recordings/TestAccConnectionDatabase.yaml new file mode 100644 index 000000000..c5255583e --- /dev/null +++ b/test/data/recordings/TestAccConnectionDatabase.yaml @@ -0,0 +1,326 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 854 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + {"name":"Acceptance-Test-Connection-TestAccConnectionDatabase","strategy":"auth0","is_domain_connection":true,"metadata":{"key1":"foo","key2":"bar"},"options":{"mfa":{"active":true,"return_enroll_settings":true},"validation":{"username":{"max":40,"min":10}},"passwordPolicy":"fair","password_history":{"enable":true,"size":5},"password_no_personal_info":{"enable":true},"password_dictionary":{"dictionary":["1234","admin","password"],"enable":true},"password_complexity_options":{"min_length":6},"enabledDatabaseCustomization":false,"brute_force_protection":true,"import_mode":false,"disable_signup":false,"requires_username":true,"customScripts":{"get_user":"myFunction"},"configuration":{"foo":"bar"},"non_persistent_attrs":["gender","hair_color"],"upstream_params":{"screen_name":{"alias":"login_hint"}},"disable_self_service_change_password":false}} + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: false + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":true},"passwordPolicy":"fair","validation":{"username":{"max":40,"min":10}},"password_history":{"enable":true,"size":5},"password_no_personal_info":{"enable":true},"password_dictionary":{"dictionary":["1234","admin","password"],"enable":true},"password_complexity_options":{"min_length":6},"enabledDatabaseCustomization":false,"brute_force_protection":true,"import_mode":false,"disable_signup":false,"requires_username":true,"customScripts":{"get_user":"myFunction"},"non_persistent_attrs":["gender","hair_color"],"upstream_params":{"screen_name":{"alias":"login_hint"}},"disable_self_service_change_password":false,"configuration":{"foo":"2.0$3e1d0f88facb17e1b112c3d9f1f96c81$28db7d584ad70c68ce729c3d094bf233$f1f3e614a938c5300015256951b80bc8d0b631a0518e52ee5ffcda68f7b9c3cb"},"strategy_version":2},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 201 Created + code: 201 + duration: 166.698125ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":true},"validation":{"username":{"max":40,"min":10}},"import_mode":false,"configuration":{"foo":"2.0$3e1d0f88facb17e1b112c3d9f1f96c81$28db7d584ad70c68ce729c3d094bf233$f1f3e614a938c5300015256951b80bc8d0b631a0518e52ee5ffcda68f7b9c3cb"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","upstream_params":{"screen_name":{"alias":"login_hint"}},"password_history":{"size":5,"enable":true},"strategy_version":2,"requires_username":true,"password_dictionary":{"enable":true,"dictionary":["1234","admin","password"]},"non_persistent_attrs":["gender","hair_color"],"brute_force_protection":true,"password_no_personal_info":{"enable":true},"password_complexity_options":{"min_length":6},"enabledDatabaseCustomization":false,"disable_self_service_change_password":false},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 112.600916ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":true},"validation":{"username":{"max":40,"min":10}},"import_mode":false,"configuration":{"foo":"2.0$3e1d0f88facb17e1b112c3d9f1f96c81$28db7d584ad70c68ce729c3d094bf233$f1f3e614a938c5300015256951b80bc8d0b631a0518e52ee5ffcda68f7b9c3cb"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","upstream_params":{"screen_name":{"alias":"login_hint"}},"password_history":{"size":5,"enable":true},"strategy_version":2,"requires_username":true,"password_dictionary":{"enable":true,"dictionary":["1234","admin","password"]},"non_persistent_attrs":["gender","hair_color"],"brute_force_protection":true,"password_no_personal_info":{"enable":true},"password_complexity_options":{"min_length":6},"enabledDatabaseCustomization":false,"disable_self_service_change_password":false},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 117.535708ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":true},"validation":{"username":{"max":40,"min":10}},"import_mode":false,"configuration":{"foo":"2.0$3e1d0f88facb17e1b112c3d9f1f96c81$28db7d584ad70c68ce729c3d094bf233$f1f3e614a938c5300015256951b80bc8d0b631a0518e52ee5ffcda68f7b9c3cb"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","upstream_params":{"screen_name":{"alias":"login_hint"}},"password_history":{"size":5,"enable":true},"strategy_version":2,"requires_username":true,"password_dictionary":{"enable":true,"dictionary":["1234","admin","password"]},"non_persistent_attrs":["gender","hair_color"],"brute_force_protection":true,"password_no_personal_info":{"enable":true},"password_complexity_options":{"min_length":6},"enabledDatabaseCustomization":false,"disable_self_service_change_password":false},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 181.150125ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":true},"validation":{"username":{"max":40,"min":10}},"import_mode":false,"configuration":{"foo":"2.0$3e1d0f88facb17e1b112c3d9f1f96c81$28db7d584ad70c68ce729c3d094bf233$f1f3e614a938c5300015256951b80bc8d0b631a0518e52ee5ffcda68f7b9c3cb"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","upstream_params":{"screen_name":{"alias":"login_hint"}},"password_history":{"size":5,"enable":true},"strategy_version":2,"requires_username":true,"password_dictionary":{"enable":true,"dictionary":["1234","admin","password"]},"non_persistent_attrs":["gender","hair_color"],"brute_force_protection":true,"password_no_personal_info":{"enable":true},"password_complexity_options":{"min_length":6},"enabledDatabaseCustomization":false,"disable_self_service_change_password":false},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 66.175875ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 608 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + {"is_domain_connection":true,"metadata":{"key1":"foo","key2":"bar"},"options":{"mfa":{"active":true,"return_enroll_settings":false},"passwordPolicy":"fair","password_history":{"enable":true,"size":5},"password_no_personal_info":{"enable":true},"enable_script_context":true,"enabledDatabaseCustomization":true,"brute_force_protection":false,"import_mode":false,"disable_signup":false,"requires_username":true,"customScripts":{"get_user":"myFunction"},"configuration":{"bar":"baz","foo":"bar"},"set_user_root_attributes":"on_first_login","non_persistent_attrs":[],"disable_self_service_change_password":true}} + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: PATCH + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":false},"import_mode":false,"configuration":{"bar":"2.0$ca9ed8fd9f7d09b04613d603c1690670$5ddb72e093491da86199eec5adfc6389$3a4c2456be72264005e7f0e1094213237534cdcacb9a9042d5a454a367b48a26","foo":"2.0$38f9a92aeb87fa283ca631e273b0fe80$9309556b3f5204e8a368de7eb1fc4ab2$6e188b6b390c290ab6e24bb7f425681f5699b94569e78f94fd2ba71356e127c4"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","password_history":{"size":5,"enable":true},"requires_username":true,"non_persistent_attrs":[],"enable_script_context":true,"brute_force_protection":false,"set_user_root_attributes":"on_first_login","password_no_personal_info":{"enable":true},"enabledDatabaseCustomization":true,"disable_self_service_change_password":true},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 134.69275ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":false},"import_mode":false,"configuration":{"bar":"2.0$ca9ed8fd9f7d09b04613d603c1690670$5ddb72e093491da86199eec5adfc6389$3a4c2456be72264005e7f0e1094213237534cdcacb9a9042d5a454a367b48a26","foo":"2.0$38f9a92aeb87fa283ca631e273b0fe80$9309556b3f5204e8a368de7eb1fc4ab2$6e188b6b390c290ab6e24bb7f425681f5699b94569e78f94fd2ba71356e127c4"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","password_history":{"size":5,"enable":true},"requires_username":true,"non_persistent_attrs":[],"enable_script_context":true,"brute_force_protection":false,"set_user_root_attributes":"on_first_login","password_no_personal_info":{"enable":true},"enabledDatabaseCustomization":true,"disable_self_service_change_password":true},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 100.072541ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":"con_GKorkjDaz6jzqngs","options":{"mfa":{"active":true,"return_enroll_settings":false},"import_mode":false,"configuration":{"bar":"2.0$ca9ed8fd9f7d09b04613d603c1690670$5ddb72e093491da86199eec5adfc6389$3a4c2456be72264005e7f0e1094213237534cdcacb9a9042d5a454a367b48a26","foo":"2.0$38f9a92aeb87fa283ca631e273b0fe80$9309556b3f5204e8a368de7eb1fc4ab2$6e188b6b390c290ab6e24bb7f425681f5699b94569e78f94fd2ba71356e127c4"},"customScripts":{"get_user":"myFunction"},"disable_signup":false,"passwordPolicy":"fair","password_history":{"size":5,"enable":true},"requires_username":true,"non_persistent_attrs":[],"enable_script_context":true,"brute_force_protection":false,"set_user_root_attributes":"on_first_login","password_no_personal_info":{"enable":true},"enabledDatabaseCustomization":true,"disable_self_service_change_password":true},"strategy":"auth0","name":"Acceptance-Test-Connection-TestAccConnectionDatabase","is_domain_connection":true,"enabled_clients":[],"realms":["Acceptance-Test-Connection-TestAccConnectionDatabase"],"metadata":{"key1":"foo","key2":"bar"}}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 118.519416ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: terraform-provider-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/0.17.2 + url: https://terraform-provider-auth0-dev.eu.auth0.com/api/v2/connections/con_GKorkjDaz6jzqngs + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 41 + uncompressed: false + body: '{"deleted_at":"2023-06-21T13:39:54.597Z"}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 202 Accepted + code: 202 + duration: 136.724875ms