diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1fb1161f77..ffd98570d0 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -273,6 +273,10 @@ Validation changes: - `default_secondary_roles` - only 1-element lists with `"ALL"` element are now supported. Check [Snowflake docs](https://docs.snowflake.com/en/sql-reference/sql/create-user#optional-object-properties-objectproperties) for more details. #### *(breaking change)* refactored snowflake_users datasource +> **IMPORTANT NOTE:** when querying users you don't have permissions to, the querying options are limited. +You won't get almost any field in `show_output` (only empty or default values), the DESCRIBE command cannot be called, so you have to set `with_describe = false`. +Only `parameters` output is not affected by the lack of privileges. + Changes: - account checking logic was entirely removed - `pattern` renamed to `like` diff --git a/docs/data-sources/users.md b/docs/data-sources/users.md index 3ba291158e..e59fc18b17 100644 --- a/docs/data-sources/users.md +++ b/docs/data-sources/users.md @@ -2,14 +2,14 @@ page_title: "snowflake_users Data Source - terraform-provider-snowflake" subcategory: "" description: |- - Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for SHOW USERS https://docs.snowflake.com/en/sql-reference/sql/show-users query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection. + Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for SHOW USERS https://docs.snowflake.com/en/sql-reference/sql/show-users query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection. Important note is that when querying users you don't have permissions to, the querying options are limited. You won't get almost any field in show_output (only empty or default values), the DESCRIBE command cannot be called, so you have to set with_describe = false. Only parameters output is not affected by the lack of privileges. --- !> **V1 release candidate** This data source was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the data source if needed. Any errors reported will be resolved with a higher priority. We encourage checking this data source out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_users (Data Source) -Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for [SHOW USERS](https://docs.snowflake.com/en/sql-reference/sql/show-users) query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection. +Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for [SHOW USERS](https://docs.snowflake.com/en/sql-reference/sql/show-users) query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection. Important note is that when querying users you don't have permissions to, the querying options are limited. You won't get almost any field in `show_output` (only empty or default values), the DESCRIBE command cannot be called, so you have to set `with_describe = false`. Only `parameters` output is not affected by the lack of privileges. ## Example Usage @@ -145,6 +145,7 @@ Read-Only: - `ext_authn_duo` (Boolean) - `ext_authn_uid` (String) - `first_name` (String) +- `has_mfa` (Boolean) - `last_name` (String) - `login_name` (String) - `middle_name` (String) diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/user_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/user_snowflake_ext.go index f6dcf68819..328f7a7c56 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/user_snowflake_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/user_snowflake_ext.go @@ -51,6 +51,17 @@ func (w *UserAssert) HasCreatedOnNotEmpty() *UserAssert { return w } +func (w *UserAssert) HasOwnerNotEmpty() *UserAssert { + w.AddAssertion(func(t *testing.T, o *sdk.User) error { + t.Helper() + if o.Owner == "" { + return fmt.Errorf("expected owner not empty; got empty") + } + return nil + }) + return w +} + func (w *UserAssert) HasLastSuccessLoginEmpty() *UserAssert { w.AddAssertion(func(t *testing.T, o *sdk.User) error { t.Helper() diff --git a/pkg/datasources/users.go b/pkg/datasources/users.go index 18fe56371a..a2e576a2ac 100644 --- a/pkg/datasources/users.go +++ b/pkg/datasources/users.go @@ -93,7 +93,7 @@ func Users() *schema.Resource { return &schema.Resource{ ReadContext: ReadUsers, Schema: usersSchema, - Description: "Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for [SHOW USERS](https://docs.snowflake.com/en/sql-reference/sql/show-users) query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection.", + Description: "Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for [SHOW USERS](https://docs.snowflake.com/en/sql-reference/sql/show-users) query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection. Important note is that when querying users you don't have permissions to, the querying options are limited. You won't get almost any field in `show_output` (only empty or default values), the DESCRIBE command cannot be called, so you have to set `with_describe = false`. Only `parameters` output is not affected by the lack of privileges.", } } diff --git a/pkg/datasources/users_acceptance_test.go b/pkg/datasources/users_acceptance_test.go index 1ea103e079..cb5c39e3d7 100644 --- a/pkg/datasources/users_acceptance_test.go +++ b/pkg/datasources/users_acceptance_test.go @@ -80,7 +80,8 @@ func TestAcc_Users_Complete(t *testing.T) { HasDefaultSecondaryRoles(`["ALL"]`). HasMinsToBypassMfaNotEmpty(). HasHasRsaPublicKey(true). - HasComment(comment), + HasComment(comment). + HasHasMfa(false), resourceparametersassert.UsersDatasourceParameters(t, "snowflake_users.test"). HasAllDefaults(), assert.Check(resource.TestCheckResourceAttr("data.snowflake_users.test", "users.0.describe_output.0.name", id.Name())), @@ -113,6 +114,7 @@ func TestAcc_Users_Complete(t *testing.T) { assert.Check(resource.TestCheckResourceAttrSet("data.snowflake_users.test", "users.0.describe_output.0.password_last_set_time")), assert.Check(resource.TestCheckResourceAttr("data.snowflake_users.test", "users.0.describe_output.0.custom_landing_page_url", "")), assert.Check(resource.TestCheckResourceAttr("data.snowflake_users.test", "users.0.describe_output.0.custom_landing_page_url_flush_next_ui_load", "false")), + assert.Check(resource.TestCheckResourceAttr("data.snowflake_users.test", "users.0.describe_output.0.has_mfa", "false")), ), }, { diff --git a/pkg/resources/user_acceptance_test.go b/pkg/resources/user_acceptance_test.go index 50dace3a99..2cef1cea23 100644 --- a/pkg/resources/user_acceptance_test.go +++ b/pkg/resources/user_acceptance_test.go @@ -1102,3 +1102,108 @@ func TestAcc_User_handleChangesToDefaultSecondaryRoles(t *testing.T) { }, }) } + +func TestAcc_User_ParameterValidationsAndDiffSuppressions(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + + userModel := model.User("w", id.Name()). + WithBinaryInputFormat(strings.ToLower(string(sdk.BinaryInputFormatHex))). + WithBinaryOutputFormat(strings.ToLower(string(sdk.BinaryOutputFormatHex))). + WithGeographyOutputFormat(strings.ToLower(string(sdk.GeographyOutputFormatGeoJSON))). + WithGeometryOutputFormat(strings.ToLower(string(sdk.GeometryOutputFormatGeoJSON))). + WithLogLevel(strings.ToLower(string(sdk.LogLevelInfo))). + WithTimestampTypeMapping(strings.ToLower(string(sdk.TimestampTypeMappingNtz))). + WithTraceLevel(strings.ToLower(string(sdk.TraceLevelAlways))) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.User), + Steps: []resource.TestStep{ + { + Config: config.FromModel(t, userModel), + Check: assert.AssertThat(t, + resourceassert.UserResource(t, userModel.ResourceReference()). + HasBinaryInputFormatString(string(sdk.BinaryInputFormatHex)). + HasBinaryOutputFormatString(string(sdk.BinaryOutputFormatHex)). + HasGeographyOutputFormatString(string(sdk.GeographyOutputFormatGeoJSON)). + HasGeometryOutputFormatString(string(sdk.GeometryOutputFormatGeoJSON)). + HasLogLevelString(string(sdk.LogLevelInfo)). + HasTimestampTypeMappingString(string(sdk.TimestampTypeMappingNtz)). + HasTraceLevelString(string(sdk.TraceLevelAlways)), + ), + }, + }, + }) +} + +func TestAcc_User_LoginNameAndDisplayName(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + newId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + + userModelWithoutBoth := model.User("w", id.Name()) + userModelWithNewId := model.User("w", newId.Name()) + userModelWithBoth := model.User("w", newId.Name()).WithLoginName("login_name").WithDisplayName("display_name") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.User), + Steps: []resource.TestStep{ + // Create without both set + { + Config: config.FromModel(t, userModelWithoutBoth), + Check: assert.AssertThat(t, + resourceassert.UserResource(t, userModelWithoutBoth.ResourceReference()). + HasNoDisplayName(). + HasNoLoginName(), + objectassert.User(t, id). + HasDisplayName(strings.ToUpper(id.Name())). + HasLoginName(strings.ToUpper(id.Name())), + ), + }, + // Rename + { + Config: config.FromModel(t, userModelWithNewId), + Check: assert.AssertThat(t, + resourceassert.UserResource(t, userModelWithNewId.ResourceReference()). + HasNoDisplayName(). + HasNoLoginName(), + objectassert.User(t, newId). + HasDisplayName(strings.ToUpper(id.Name())). + HasLoginName(strings.ToUpper(id.Name())), + ), + }, + // Set both params + { + Config: config.FromModel(t, userModelWithBoth), + Check: assert.AssertThat(t, + resourceassert.UserResource(t, userModelWithBoth.ResourceReference()). + HasDisplayNameString("display_name"). + HasLoginNameString("login_name"), + objectassert.User(t, newId). + HasDisplayName("display_name"). + HasLoginName("LOGIN_NAME"), + ), + }, + // Unset both params + { + Config: config.FromModel(t, userModelWithNewId), + Check: assert.AssertThat(t, + resourceassert.UserResource(t, userModelWithNewId.ResourceReference()). + HasDisplayNameString(""). + HasLoginNameString(""), + objectassert.User(t, newId). + HasDisplayName(""). + HasLoginName(strings.ToUpper(newId.Name())), + ), + }, + }, + }) +} diff --git a/pkg/resources/user_parameters.go b/pkg/resources/user_parameters.go index 498c8d7bcd..968f2c7c3c 100644 --- a/pkg/resources/user_parameters.go +++ b/pkg/resources/user_parameters.go @@ -76,13 +76,12 @@ var ( ) ) -// TODO [SNOW-1348101 - next PR]: uncomment DiffSuppress and ValidateDiag type parameterDef[T ~string] struct { - Name T - Type schema.ValueType - Description string - // DiffSuppress schema.SchemaDiffSuppressFunc - // ValidateDiag schema.SchemaValidateDiagFunc + Name T + Type schema.ValueType + Description string + DiffSuppress schema.SchemaDiffSuppressFunc + ValidateDiag schema.SchemaValidateDiagFunc } func init() { @@ -91,8 +90,8 @@ func init() { // session params {Name: sdk.UserParameterAbortDetachedQuery, Type: schema.TypeBool, Description: "Specifies the action that Snowflake performs for in-progress queries if connectivity is lost due to abrupt termination of a session (e.g. network outage, browser termination, service interruption)."}, {Name: sdk.UserParameterAutocommit, Type: schema.TypeBool, Description: "Specifies whether autocommit is enabled for the session. Autocommit determines whether a DML statement, when executed without an active transaction, is automatically committed after the statement successfully completes. For more information, see [Transactions](https://docs.snowflake.com/en/sql-reference/transactions)."}, - {Name: sdk.UserParameterBinaryInputFormat, Type: schema.TypeString, Description: "The format of VARCHAR values passed as input to VARCHAR-to-BINARY conversion functions. For more information, see [Binary input and output](https://docs.snowflake.com/en/sql-reference/binary-input-output)."}, - {Name: sdk.UserParameterBinaryOutputFormat, Type: schema.TypeString, Description: "The format for VARCHAR values returned as output by BINARY-to-VARCHAR conversion functions. For more information, see [Binary input and output](https://docs.snowflake.com/en/sql-reference/binary-input-output)."}, + {Name: sdk.UserParameterBinaryInputFormat, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToBinaryInputFormat), DiffSuppress: NormalizeAndCompare(sdk.ToBinaryInputFormat), Description: "The format of VARCHAR values passed as input to VARCHAR-to-BINARY conversion functions. For more information, see [Binary input and output](https://docs.snowflake.com/en/sql-reference/binary-input-output)."}, + {Name: sdk.UserParameterBinaryOutputFormat, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToBinaryOutputFormat), DiffSuppress: NormalizeAndCompare(sdk.ToBinaryOutputFormat), Description: "The format for VARCHAR values returned as output by BINARY-to-VARCHAR conversion functions. For more information, see [Binary input and output](https://docs.snowflake.com/en/sql-reference/binary-input-output)."}, {Name: sdk.UserParameterClientMemoryLimit, Type: schema.TypeInt, Description: "Parameter that specifies the maximum amount of memory the JDBC driver or ODBC driver should use for the result set from queries (in MB)."}, {Name: sdk.UserParameterClientMetadataRequestUseConnectionCtx, Type: schema.TypeBool, Description: "For specific ODBC functions and JDBC methods, this parameter can change the default search scope from all databases/schemas to the current database/schema. The narrower search typically returns fewer rows and executes more quickly."}, {Name: sdk.UserParameterClientPrefetchThreads, Type: schema.TypeInt, Description: "Parameter that specifies the number of threads used by the client to pre-fetch large result sets. The driver will attempt to honor the parameter value, but defines the minimum and maximum values (depending on your system’s resources) to improve performance."}, @@ -106,14 +105,14 @@ func init() { {Name: sdk.UserParameterEnableUnloadPhysicalTypeOptimization, Type: schema.TypeBool, Description: "Specifies whether to set the schema for unloaded Parquet files based on the logical column data types (i.e. the types in the unload SQL query or source table) or on the unloaded column values (i.e. the smallest data types and precision that support the values in the output columns of the unload SQL statement or source table)."}, {Name: sdk.UserParameterErrorOnNondeterministicMerge, Type: schema.TypeBool, Description: "Specifies whether to return an error when the [MERGE](https://docs.snowflake.com/en/sql-reference/sql/merge) command is used to update or delete a target row that joins multiple source rows and the system cannot determine the action to perform on the target row."}, {Name: sdk.UserParameterErrorOnNondeterministicUpdate, Type: schema.TypeBool, Description: "Specifies whether to return an error when the [UPDATE](https://docs.snowflake.com/en/sql-reference/sql/update) command is used to update a target row that joins multiple source rows and the system cannot determine the action to perform on the target row."}, - {Name: sdk.UserParameterGeographyOutputFormat, Type: schema.TypeString, Description: "Display format for [GEOGRAPHY values](https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html#label-data-types-geography)."}, - {Name: sdk.UserParameterGeometryOutputFormat, Type: schema.TypeString, Description: "Display format for [GEOMETRY values](https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html#label-data-types-geometry)."}, + {Name: sdk.UserParameterGeographyOutputFormat, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToGeographyOutputFormat), DiffSuppress: NormalizeAndCompare(sdk.ToGeographyOutputFormat), Description: "Display format for [GEOGRAPHY values](https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html#label-data-types-geography)."}, + {Name: sdk.UserParameterGeometryOutputFormat, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToGeometryOutputFormat), DiffSuppress: NormalizeAndCompare(sdk.ToGeometryOutputFormat), Description: "Display format for [GEOMETRY values](https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html#label-data-types-geometry)."}, {Name: sdk.UserParameterJdbcTreatDecimalAsInt, Type: schema.TypeBool, Description: "Specifies how JDBC processes columns that have a scale of zero (0)."}, {Name: sdk.UserParameterJdbcTreatTimestampNtzAsUtc, Type: schema.TypeBool, Description: "Specifies how JDBC processes TIMESTAMP_NTZ values."}, {Name: sdk.UserParameterJdbcUseSessionTimezone, Type: schema.TypeBool, Description: "Specifies whether the JDBC Driver uses the time zone of the JVM or the time zone of the session (specified by the [TIMEZONE](https://docs.snowflake.com/en/sql-reference/parameters#label-timezone) parameter) for the getDate(), getTime(), and getTimestamp() methods of the ResultSet class."}, {Name: sdk.UserParameterJsonIndent, Type: schema.TypeInt, Description: "Specifies the number of blank spaces to indent each new element in JSON output in the session. Also specifies whether to insert newline characters after each element."}, {Name: sdk.UserParameterLockTimeout, Type: schema.TypeInt, Description: "Number of seconds to wait while trying to lock a resource, before timing out and aborting the statement."}, - {Name: sdk.UserParameterLogLevel, Type: schema.TypeString, Description: "Specifies the severity level of messages that should be ingested and made available in the active event table. Messages at the specified level (and at more severe levels) are ingested. For more information about log levels, see [Setting log level](https://docs.snowflake.com/en/developer-guide/logging-tracing/logging-log-level)."}, + {Name: sdk.UserParameterLogLevel, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToLogLevel), DiffSuppress: NormalizeAndCompare(sdk.ToLogLevel), Description: "Specifies the severity level of messages that should be ingested and made available in the active event table. Messages at the specified level (and at more severe levels) are ingested. For more information about log levels, see [Setting log level](https://docs.snowflake.com/en/developer-guide/logging-tracing/logging-log-level)."}, {Name: sdk.UserParameterMultiStatementCount, Type: schema.TypeInt, Description: "Number of statements to execute when using the multi-statement capability."}, {Name: sdk.UserParameterNoorderSequenceAsDefault, Type: schema.TypeBool, Description: "Specifies whether the ORDER or NOORDER property is set by default when you create a new sequence or add a new table column. The ORDER and NOORDER properties determine whether or not the values are generated for the sequence or auto-incremented column in [increasing or decreasing order](https://docs.snowflake.com/en/user-guide/querying-sequences.html#label-querying-sequences-increasing-values)."}, {Name: sdk.UserParameterOdbcTreatDecimalAsInt, Type: schema.TypeBool, Description: "Specifies how ODBC processes columns that have a scale of zero (0)."}, @@ -131,12 +130,12 @@ func init() { {Name: sdk.UserParameterTimestampLtzOutputFormat, Type: schema.TypeString, Description: "Specifies the display format for the TIMESTAMP_LTZ data type. If no format is specified, defaults to [TIMESTAMP_OUTPUT_FORMAT](https://docs.snowflake.com/en/sql-reference/parameters#label-timestamp-output-format). For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output)."}, {Name: sdk.UserParameterTimestampNtzOutputFormat, Type: schema.TypeString, Description: "Specifies the display format for the TIMESTAMP_NTZ data type."}, {Name: sdk.UserParameterTimestampOutputFormat, Type: schema.TypeString, Description: "Specifies the display format for the TIMESTAMP data type alias. For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output)."}, - {Name: sdk.UserParameterTimestampTypeMapping, Type: schema.TypeString, Description: "Specifies the TIMESTAMP_* variation that the TIMESTAMP data type alias maps to."}, + {Name: sdk.UserParameterTimestampTypeMapping, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToTimestampTypeMapping), DiffSuppress: NormalizeAndCompare(sdk.ToTimestampTypeMapping), Description: "Specifies the TIMESTAMP_* variation that the TIMESTAMP data type alias maps to."}, {Name: sdk.UserParameterTimestampTzOutputFormat, Type: schema.TypeString, Description: "Specifies the display format for the TIMESTAMP_TZ data type. If no format is specified, defaults to [TIMESTAMP_OUTPUT_FORMAT](https://docs.snowflake.com/en/sql-reference/parameters#label-timestamp-output-format). For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output)."}, {Name: sdk.UserParameterTimezone, Type: schema.TypeString, Description: "Specifies the time zone for the session. You can specify a [time zone name](https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab) or a [link name](https://data.iana.org/time-zones/tzdb-2021a/backward) from release 2021a of the [IANA Time Zone Database](https://www.iana.org/time-zones) (e.g. America/Los_Angeles, Europe/London, UTC, Etc/GMT, etc.)."}, {Name: sdk.UserParameterTimeInputFormat, Type: schema.TypeString, Description: "Specifies the input format for the TIME data type. For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output). Any valid, supported time format or AUTO (AUTO specifies that Snowflake attempts to automatically detect the format of times stored in the system during the session)."}, {Name: sdk.UserParameterTimeOutputFormat, Type: schema.TypeString, Description: "Specifies the display format for the TIME data type. For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output)."}, - {Name: sdk.UserParameterTraceLevel, Type: schema.TypeString, Description: "Controls how trace events are ingested into the event table. For more information about trace levels, see [Setting trace level](https://docs.snowflake.com/en/developer-guide/logging-tracing/tracing-trace-level)."}, + {Name: sdk.UserParameterTraceLevel, Type: schema.TypeString, ValidateDiag: sdkValidation(sdk.ToTraceLevel), DiffSuppress: NormalizeAndCompare(sdk.ToTraceLevel), Description: "Controls how trace events are ingested into the event table. For more information about trace levels, see [Setting trace level](https://docs.snowflake.com/en/developer-guide/logging-tracing/tracing-trace-level)."}, {Name: sdk.UserParameterTransactionAbortOnError, Type: schema.TypeBool, Description: "Specifies the action to perform when a statement issued within a non-autocommit transaction returns with an error."}, {Name: sdk.UserParameterTransactionDefaultIsolationLevel, Type: schema.TypeString, Description: "Specifies the isolation level for transactions in the user session."}, {Name: sdk.UserParameterTwoDigitCenturyStart, Type: schema.TypeInt, Description: "Specifies the “century start” year for 2-digit years (i.e. the earliest year such dates can represent). This parameter prevents ambiguous dates when importing or converting data with the `YY` date format component (i.e. years represented as 2 digits)."}, @@ -154,13 +153,12 @@ func init() { fieldName := strings.ToLower(string(field.Name)) userParametersSchema[fieldName] = &schema.Schema{ - Type: field.Type, - Description: enrichWithReferenceToParameterDocs(field.Name, field.Description), - Computed: true, - Optional: true, - // TODO [SNOW-1348101 - next PR]: uncomment and fill out - // ValidateDiagFunc: field.ValidateDiag, - // DiffSuppressFunc: field.DiffSuppress, + Type: field.Type, + Description: enrichWithReferenceToParameterDocs(field.Name, field.Description), + Computed: true, + Optional: true, + ValidateDiagFunc: field.ValidateDiag, + DiffSuppressFunc: field.DiffSuppress, } } } diff --git a/pkg/schemas/user_describe.go b/pkg/schemas/user_describe.go index 8292999d39..acefcfed12 100644 --- a/pkg/schemas/user_describe.go +++ b/pkg/schemas/user_describe.go @@ -127,6 +127,10 @@ var UserDescribeSchema = map[string]*schema.Schema{ Type: schema.TypeBool, Computed: true, }, + "has_mfa": { + Type: schema.TypeBool, + Computed: true, + }, } var _ = UserDescribeSchema @@ -223,6 +227,9 @@ func UserDescriptionToSchema(userDetails sdk.UserDetails) []map[string]any { if userDetails.CustomLandingPageUrlFlushNextUiLoad != nil { userDetailsSchema["custom_landing_page_url_flush_next_ui_load"] = userDetails.CustomLandingPageUrlFlushNextUiLoad.Value } + if userDetails.HasMfa != nil { + userDetailsSchema["has_mfa"] = userDetails.HasMfa.Value + } return []map[string]any{ userDetailsSchema, } diff --git a/pkg/sdk/parameters.go b/pkg/sdk/parameters.go index 877c91cc09..79445c3eae 100644 --- a/pkg/sdk/parameters.go +++ b/pkg/sdk/parameters.go @@ -839,7 +839,7 @@ func ToBinaryInputFormat(s string) (BinaryInputFormat, error) { return BinaryInputFormatHex, nil case string(BinaryInputFormatBase64): return BinaryInputFormatBase64, nil - case string(BinaryInputFormatUTF8): + case string(BinaryInputFormatUTF8), "UTF-8": return BinaryInputFormatUTF8, nil default: return "", fmt.Errorf("invalid binary input format: %s", s) diff --git a/pkg/sdk/testint/users_integration_test.go b/pkg/sdk/testint/users_integration_test.go index 43c17f4328..759ea08189 100644 --- a/pkg/sdk/testint/users_integration_test.go +++ b/pkg/sdk/testint/users_integration_test.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "testing" + "time" assertions "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" @@ -1034,7 +1035,6 @@ func TestInt_Users(t *testing.T) { // This test proves issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2817. // sql: Scan error on column index 10, name "disabled": sql/driver: couldn't convert "null" into type bool - // TODO [SNOW-1348101 - next PR]: test what is visible if everything is set if there is no ownership on the active role t.Run("issue #2817: handle show properly without OWNERSHIP and MANAGE GRANTS", func(t *testing.T) { disabledUser, disabledUserCleanup := testClientHelper().User.CreateUserWithOptions(t, testClientHelper().Ids.RandomAccountObjectIdentifier(), &sdk.CreateUserOptions{ObjectProperties: &sdk.UserObjectProperties{Disable: sdk.Bool(true)}}) t.Cleanup(disabledUserCleanup) @@ -1074,6 +1074,137 @@ func TestInt_Users(t *testing.T) { require.Nil(t, fetchedDisabledUserDetails) }) + t.Run("issue #2817: check what fields are available when using a user with insufficient privileges to fully inspect another user", func(t *testing.T) { + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + err := client.Users.Create(ctx, id, &sdk.CreateUserOptions{ + SessionParameters: &sdk.SessionParameters{ + AbortDetachedQuery: sdk.Bool(true), + Autocommit: sdk.Bool(false), + BinaryInputFormat: sdk.Pointer(sdk.BinaryInputFormatUTF8), + BinaryOutputFormat: sdk.Pointer(sdk.BinaryOutputFormatBase64), + ClientMemoryLimit: sdk.Int(1024), + ClientMetadataRequestUseConnectionCtx: sdk.Bool(true), + ClientPrefetchThreads: sdk.Int(2), + ClientResultChunkSize: sdk.Int(48), + ClientResultColumnCaseInsensitive: sdk.Bool(true), + ClientSessionKeepAlive: sdk.Bool(true), + ClientSessionKeepAliveHeartbeatFrequency: sdk.Int(2400), + ClientTimestampTypeMapping: sdk.Pointer(sdk.ClientTimestampTypeMappingNtz), + DateInputFormat: sdk.String("YYYY-MM-DD"), + DateOutputFormat: sdk.String("YY-MM-DD"), + EnableUnloadPhysicalTypeOptimization: sdk.Bool(false), + ErrorOnNondeterministicMerge: sdk.Bool(false), + ErrorOnNondeterministicUpdate: sdk.Bool(true), + GeographyOutputFormat: sdk.Pointer(sdk.GeographyOutputFormatWKB), + GeometryOutputFormat: sdk.Pointer(sdk.GeometryOutputFormatWKB), + JdbcTreatDecimalAsInt: sdk.Bool(false), + JdbcTreatTimestampNtzAsUtc: sdk.Bool(true), + JdbcUseSessionTimezone: sdk.Bool(false), + JSONIndent: sdk.Int(4), + LockTimeout: sdk.Int(21222), + LogLevel: sdk.Pointer(sdk.LogLevelError), + MultiStatementCount: sdk.Int(0), + NoorderSequenceAsDefault: sdk.Bool(false), + OdbcTreatDecimalAsInt: sdk.Bool(true), + QueryTag: sdk.String("some_tag"), + QuotedIdentifiersIgnoreCase: sdk.Bool(true), + RowsPerResultset: sdk.Int(2), + S3StageVpceDnsName: sdk.String("vpce-id.s3.region.vpce.amazonaws.com"), + SearchPath: sdk.String("$public, $current"), + SimulatedDataSharingConsumer: sdk.String("some_consumer"), + StatementQueuedTimeoutInSeconds: sdk.Int(10), + StatementTimeoutInSeconds: sdk.Int(10), + StrictJSONOutput: sdk.Bool(true), + TimestampDayIsAlways24h: sdk.Bool(true), + TimestampInputFormat: sdk.String("YYYY-MM-DD"), + TimestampLTZOutputFormat: sdk.String("YYYY-MM-DD HH24:MI:SS"), + TimestampNTZOutputFormat: sdk.String("YYYY-MM-DD HH24:MI:SS"), + TimestampOutputFormat: sdk.String("YYYY-MM-DD HH24:MI:SS"), + TimestampTypeMapping: sdk.Pointer(sdk.TimestampTypeMappingLtz), + TimestampTZOutputFormat: sdk.String("YYYY-MM-DD HH24:MI:SS"), + Timezone: sdk.String("Europe/Warsaw"), + TimeInputFormat: sdk.String("HH24:MI"), + TimeOutputFormat: sdk.String("HH24:MI"), + TraceLevel: sdk.Pointer(sdk.TraceLevelOnEvent), + TransactionAbortOnError: sdk.Bool(true), + TransactionDefaultIsolationLevel: sdk.Pointer(sdk.TransactionDefaultIsolationLevelReadCommitted), + TwoDigitCenturyStart: sdk.Int(1980), + UnsupportedDDLAction: sdk.Pointer(sdk.UnsupportedDDLActionFail), + UseCachedResult: sdk.Bool(false), + WeekOfYearPolicy: sdk.Int(1), + WeekStart: sdk.Int(1), + }, + ObjectParameters: &sdk.UserObjectParameters{ + EnableUnredactedQuerySyntaxError: sdk.Bool(true), + NetworkPolicy: sdk.Pointer(networkPolicy.ID()), + PreventUnloadToInternalStages: sdk.Bool(true), + }, + ObjectProperties: &sdk.UserObjectProperties{ + Password: sdk.String(password), + LoginName: sdk.String(newValue), + DisplayName: sdk.String(newValue), + FirstName: sdk.String(newValue), + MiddleName: sdk.String(newValue), + LastName: sdk.String(newValue), + Email: sdk.String(email), + MustChangePassword: sdk.Bool(true), + Disable: sdk.Bool(true), + DaysToExpiry: sdk.Int(5), + MinsToUnlock: sdk.Int(15), + DefaultWarehouse: sdk.Pointer(warehouseId), + DefaultNamespace: sdk.Pointer(schemaIdObjectIdentifier), + DefaultRole: sdk.Pointer(roleId), + DefaultSecondaryRoles: &sdk.SecondaryRoles{}, + MinsToBypassMFA: sdk.Int(30), + RSAPublicKey: sdk.String(key), + RSAPublicKey2: sdk.String(key2), + Comment: sdk.String("some comment"), + }, + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().User.DropUserFunc(t, id)) + + role, roleCleanup := testClientHelper().Role.CreateRoleGrantedToCurrentUser(t) + t.Cleanup(roleCleanup) + + revertRole := testClientHelper().Role.UseRole(t, role.ID()) + t.Cleanup(revertRole) + + // Describe won't work and parameters are not affected by that fact + assertParametersSet(objectparametersassert.UserParameters(t, id)) + + assertions.AssertThatObject(t, objectassert.UserForIntegrationTests(t, id, testClientHelper()). + HasName(id.Name()). + HasCreatedOnNotEmpty(). + HasLoginName(""). + HasDisplayName(""). + HasFirstName(""). + HasLastName(""). + HasEmail(""). + HasMinsToUnlock(""). + HasDaysToExpiry(""). + HasComment(""). + HasDisabled(false). // underlying null + HasMustChangePassword(false). // underlying null + HasSnowflakeLock(false). // underlying null + HasDefaultWarehouse(""). + HasDefaultNamespace(""). + HasDefaultRole(""). + HasDefaultSecondaryRoles(""). + HasExtAuthnDuo(false). // underlying null + HasExtAuthnUid(""). + HasMinsToBypassMfa(""). + HasOwnerNotEmpty(). + HasLastSuccessLogin(time.Time{}). // underlying null + HasExpiresAtTimeNotEmpty(). + HasLockedUntilTimeNotEmpty(). + HasHasPassword(false). + HasHasRsaPublicKey(false). + HasType(""). // underlying null + HasHasMfa(false), + ) + }) + t.Run("login_name and display_name inconsistencies", func(t *testing.T) { id := testClientHelper().Ids.RandomAccountObjectIdentifier() diff --git a/pkg/sdk/users.go b/pkg/sdk/users.go index 8db4239978..ba1b98ef3d 100644 --- a/pkg/sdk/users.go +++ b/pkg/sdk/users.go @@ -91,9 +91,8 @@ type userDBRow struct { HasPassword bool `db:"has_password"` HasRsaPublicKey bool `db:"has_rsa_public_key"` // TODO [SNOW-1645348]: test type thoroughly - Type sql.NullString `db:"type"` - // TODO [SNOW-1348101 - next PR]: test if hasMfa is always non-nullable, check if this has mfa helps with disable mfa, add to the describe output too - HasMfa bool `db:"has_mfa"` + Type sql.NullString `db:"type"` + HasMfa bool `db:"has_mfa"` } func (row userDBRow) convert() *User { @@ -465,6 +464,7 @@ type UserDetails struct { PasswordLastSetTime *StringProperty CustomLandingPageUrl *StringProperty CustomLandingPageUrlFlushNextUiLoad *BoolProperty + HasMfa *BoolProperty } func userDetailsFromRows(rows []propertyRow) *UserDetails { @@ -531,6 +531,8 @@ func userDetailsFromRows(rows []propertyRow) *UserDetails { v.CustomLandingPageUrl = row.toStringProperty() case "CUSTOM_LANDING_PAGE_URL_FLUSH_NEXT_UI_LOAD": v.CustomLandingPageUrlFlushNextUiLoad = row.toBoolProperty() + case "HAS_MFA": + v.HasMfa = row.toBoolProperty() } } return v