diff --git a/internal/identity/mfa/duo.go b/internal/identity/mfa/duo.go index 7c39c9480..38eda875a 100644 --- a/internal/identity/mfa/duo.go +++ b/internal/identity/mfa/duo.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/util" ) const ( @@ -50,11 +51,14 @@ var duoSchemaMap = map[string]*schema.Schema{ }, } +// GetDuoSchemaResource returns the resource needed to provision an identity/mfa/duo resource. func GetDuoSchemaResource() (*schema.Resource, error) { config, _ := NewContextFuncConfig(MethodTypeDuo, PathTypeMethodID, nil, nil, map[string]string{ // API is inconsistent between create/update and read. "pushinfo": consts.FieldPushInfo, - }) + }, nil) + + config.setAPIValueGetter(consts.FieldUsernameFormat, util.GetAPIRequestValue) return getMethodSchemaResource(duoSchemaMap, config), nil } diff --git a/internal/identity/mfa/login_enforcement.go b/internal/identity/mfa/login_enforcement.go index 8fb8a4ade..eda3910cb 100644 --- a/internal/identity/mfa/login_enforcement.go +++ b/internal/identity/mfa/login_enforcement.go @@ -62,8 +62,10 @@ var loginEnforcementSchemaMap = map[string]*schema.Schema{ }, } +// GetLoginEnforcementSchemaResource returns the resource needed to provision an identity/mfa/login-enforcement +// resource. func GetLoginEnforcementSchemaResource() (*schema.Resource, error) { - config, err := NewContextFuncConfig(MethodTypeLoginEnforcement, PathTypeName, nil, nil, nil) + config, err := NewContextFuncConfig(MethodTypeLoginEnforcement, PathTypeName, nil, nil, nil, nil) if err != nil { return nil, err } diff --git a/internal/identity/mfa/mfa.go b/internal/identity/mfa/mfa.go index 3267b9a4e..ededc6bc7 100644 --- a/internal/identity/mfa/mfa.go +++ b/internal/identity/mfa/mfa.go @@ -155,15 +155,17 @@ func joinPath(root string, parts ...string) string { // contextFuncConfig provides the necessary configuration that is required by any of // any Get*ContextFunc factory functions. type contextFuncConfig struct { - mu sync.Mutex - method string - m map[string]*schema.Schema - computedOnly []string - quirksMap map[string]string - copyQuirks []string - requireLock bool - requestPath string - pt PathType + mu sync.Mutex + method string + m map[string]*schema.Schema + apiValueGetters map[string]util.VaultAPIValueGetter + defaultAPIValueGetter util.VaultAPIValueGetter + computedOnly []string + quirksMap map[string]string + copyQuirks []string + requireLock bool + requestPath string + pt PathType } // GetRequestData needed for a Vault request. Only those fields provided by @@ -251,7 +253,17 @@ func (c *contextFuncConfig) GetSecretFields() []string { // GetRequestData needed for a Vault request. Only those fields provided by // GetWriteFields() will be included in the request data. func (c *contextFuncConfig) GetRequestData(d *schema.ResourceData) map[string]interface{} { - return util.GetAPIRequestDataWithSlice(d, c.GetWriteFields()) + result := make(map[string]interface{}) + for _, k := range c.GetWriteFields() { + getter := c.getAPIValueGetter(k) + if getter == nil { + getter = c.defaultAPIValueGetter + } + if v, ok := getter(d, k); ok { + result[k] = v + } + } + return result } func (c *contextFuncConfig) Method() string { @@ -345,6 +357,20 @@ func (c *contextFuncConfig) GetIDFromResourceData(d *schema.ResourceData) (strin return c.id(idField, v) } +func (c *contextFuncConfig) setAPIValueGetter(k string, getterFunc util.VaultAPIValueGetter) { + if c.apiValueGetters == nil { + c.apiValueGetters = make(map[string]util.VaultAPIValueGetter) + } + c.apiValueGetters[k] = getterFunc +} + +func (c *contextFuncConfig) getAPIValueGetter(k string) util.VaultAPIValueGetter { + if c.apiValueGetters == nil { + return nil + } + return c.apiValueGetters[k] +} + func (c *contextFuncConfig) id(f string, v interface{}) (string, error) { id, ok := v.(string) if !ok || id == "" { @@ -354,7 +380,9 @@ func (c *contextFuncConfig) id(f string, v interface{}) (string, error) { } // NewContextFuncConfig setups a contextFuncConfig that is supported by any of any Get*ContextFunc factory functions. -func NewContextFuncConfig(method string, pt PathType, m map[string]*schema.Schema, computedOnly []string, quirksMap map[string]string) (*contextFuncConfig, error) { +func NewContextFuncConfig(method string, pt PathType, m map[string]*schema.Schema, + computedOnly []string, quirksMap map[string]string, defaultAPIValueGetter util.VaultAPIValueGetter, +) (*contextFuncConfig, error) { if len(computedOnly) == 0 { computedOnly = defaultComputedOnlyFields } @@ -373,13 +401,18 @@ func NewContextFuncConfig(method string, pt PathType, m map[string]*schema.Schem return nil, fmt.Errorf("unsupported path type %s", pt) } + if defaultAPIValueGetter == nil { + defaultAPIValueGetter = util.GetAPIRequestValueOk + } + config := &contextFuncConfig{ - method: method, - pt: pt, - m: m, - computedOnly: computedOnly, - quirksMap: quirksMap, - requireLock: true, + method: method, + pt: pt, + m: m, + computedOnly: computedOnly, + quirksMap: quirksMap, + requireLock: true, + defaultAPIValueGetter: defaultAPIValueGetter, } return config, nil @@ -397,10 +430,10 @@ func getSchemaResource(s map[string]*schema.Schema, config *contextFuncConfig, a r := &schema.Resource{ Schema: m, - CreateContext: GetCreateContextFunc(config), - UpdateContext: GetUpdateContextFunc(config), - ReadContext: GetReadContextFunc(config), - DeleteContext: GetDeleteContextFunc(config), + CreateContext: NewCreateContextFunc(config), + UpdateContext: NewUpdateContextFunc(config), + ReadContext: NewReadContextFunc(config), + DeleteContext: NewDeleteContextFunc(config), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -416,14 +449,25 @@ func getSchemaResource(s map[string]*schema.Schema, config *contextFuncConfig, a r = f(r) } + for k, s := range m { + if s.Computed { + continue + } + switch s.Type { + case schema.TypeInt, schema.TypeBool: + if f := config.getAPIValueGetter(k); f == nil { + config.setAPIValueGetter(k, util.GetAPIRequestValueOkExists) + } + } + } config.m = r.Schema return r } -// GetCreateContextFunc for a contextFuncConfig. +// NewCreateContextFunc for a contextFuncConfig. // The return function supports the path types: PathTypeName, and PathTypeMethodID -func GetCreateContextFunc(config *contextFuncConfig) schema.CreateContextFunc { +func NewCreateContextFunc(config *contextFuncConfig) schema.CreateContextFunc { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config.Lock() defer config.Unlock() @@ -477,13 +521,13 @@ func GetCreateContextFunc(config *contextFuncConfig) schema.CreateContextFunc { d.SetId(rid) - return GetReadContextFunc(config)(ctx, d, meta) + return NewReadContextFunc(config)(ctx, d, meta) } } -// GetUpdateContextFunc for a contextFuncConfig. +// NewUpdateContextFunc for a contextFuncConfig. // The return function supports the path types: PathTypeName, and PathTypeMethodID -func GetUpdateContextFunc(config *contextFuncConfig) schema.UpdateContextFunc { +func NewUpdateContextFunc(config *contextFuncConfig) schema.UpdateContextFunc { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config.Lock() defer config.Unlock() @@ -509,13 +553,13 @@ func GetUpdateContextFunc(config *contextFuncConfig) schema.UpdateContextFunc { return diag.FromErr(err) } - return GetReadContextFunc(config)(ctx, d, meta) + return NewReadContextFunc(config)(ctx, d, meta) } } -// GetReadContextFunc for a contextFuncConfig. +// NewReadContextFunc for a contextFuncConfig. // The return function supports the path types: PathTypeName, and PathTypeMethodID -func GetReadContextFunc(config *contextFuncConfig) schema.ReadContextFunc { +func NewReadContextFunc(config *contextFuncConfig) schema.ReadContextFunc { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c, dg := provider.GetClientDiag(d, meta) if dg != nil { @@ -592,9 +636,9 @@ func GetReadContextFunc(config *contextFuncConfig) schema.ReadContextFunc { } } -// GetDeleteContextFunc for a contextFuncConfig. +// NewDeleteContextFunc for a contextFuncConfig. // The return function supports the path types: PathTypeName, and PathTypeMethodID -func GetDeleteContextFunc(config *contextFuncConfig) schema.DeleteContextFunc { +func NewDeleteContextFunc(config *contextFuncConfig) schema.DeleteContextFunc { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c, dg := provider.GetClientDiag(d, meta) if dg != nil { diff --git a/internal/identity/mfa/okta.go b/internal/identity/mfa/okta.go index a55321e1c..bd019aaf5 100644 --- a/internal/identity/mfa/okta.go +++ b/internal/identity/mfa/okta.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/util" ) const ( @@ -44,8 +45,9 @@ var oktaSchemaMap = map[string]*schema.Schema{ }, } +// GetOKTASchemaResource returns the resource needed to provision an identity/mfa/okta resource. func GetOKTASchemaResource() (*schema.Resource, error) { - config, err := NewContextFuncConfig(MethodTypeOKTA, PathTypeMethodID, nil, nil, nil) + config, err := NewContextFuncConfig(MethodTypeOKTA, PathTypeMethodID, nil, nil, nil, nil) // TODO: the primary_email field is not included in the response // from vault-10.x and up. Its value will be derived the from resource data // if not present in the response from Vault. @@ -54,5 +56,7 @@ func GetOKTASchemaResource() (*schema.Resource, error) { return nil, err } + config.setAPIValueGetter(consts.FieldUsernameFormat, util.GetAPIRequestValue) + return getMethodSchemaResource(oktaSchemaMap, config), nil } diff --git a/internal/identity/mfa/ping_id.go b/internal/identity/mfa/ping_id.go index 1b71bcd83..8a53670ee 100644 --- a/internal/identity/mfa/ping_id.go +++ b/internal/identity/mfa/ping_id.go @@ -65,6 +65,7 @@ var pingIDSchemaMap = map[string]*schema.Schema{ }, } +// GetPingIDSchemaResource returns the resource needed to provision an identity/mfa/pingid resource. func GetPingIDSchemaResource() (*schema.Resource, error) { config, err := NewContextFuncConfig(MethodTypePingID, PathTypeMethodID, nil, []string{ consts.FieldType, @@ -73,7 +74,7 @@ func GetPingIDSchemaResource() (*schema.Resource, error) { consts.FieldAdminURL, consts.FieldAuthenticatorURL, consts.FieldOrgAlias, - }, nil) + }, nil, nil) if err != nil { return nil, err } diff --git a/internal/identity/mfa/totp.go b/internal/identity/mfa/totp.go index e6a3d88eb..6d7c07848 100644 --- a/internal/identity/mfa/totp.go +++ b/internal/identity/mfa/totp.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-provider-vault/internal/consts" "github.com/hashicorp/terraform-provider-vault/internal/provider" + "github.com/hashicorp/terraform-provider-vault/util" ) const ( @@ -46,6 +47,7 @@ var ( consts.FieldQRSize: { Type: schema.TypeInt, Computed: true, + Optional: true, Description: `The pixel size of the generated square QR code.`, }, consts.FieldAlgorithm: { @@ -78,11 +80,15 @@ var ( } ) +// GetTOTPSchemaResource returns the resource needed to provision an identity/mfa/totp resource. func GetTOTPSchemaResource() (*schema.Resource, error) { - config, err := NewContextFuncConfig(MethodTypeTOTP, PathTypeMethodID, nil, nil, nil) + config, err := NewContextFuncConfig(MethodTypeTOTP, PathTypeMethodID, nil, nil, nil, nil) if err != nil { return nil, err } + // ensure that the qr_size field can be set to 0 + config.setAPIValueGetter(consts.FieldQRSize, util.GetAPIRequestValueOkExists) + return getMethodSchemaResource(totpSchemaMap, config), nil } diff --git a/util/util.go b/util/util.go index 439b10d77..16e434909 100644 --- a/util/util.go +++ b/util/util.go @@ -21,7 +21,13 @@ import ( "github.com/hashicorp/terraform-provider-vault/internal/consts" ) -func JsonDiffSuppress(k, old, new string, d *schema.ResourceData) bool { +type ( + // VaultAPIValueGetter returns the value from the *schema.ResourceData for a key, + // along with a boolean that denotes the key's existence. + VaultAPIValueGetter func(*schema.ResourceData, string) (interface{}, bool) +) + +func JsonDiffSuppress(k, old, new string, _ *schema.ResourceData) bool { var oldJSON, newJSON interface{} err := json.Unmarshal([]byte(old), &oldJSON) if err != nil { @@ -193,7 +199,7 @@ func ParsePath(userSuppliedPath, endpoint string, d *schema.ResourceData) string if !ok { continue } - // All path parameters must be strings so it's safe to + // All path parameters must be strings, so it's safe to // assume here. val := valRaw.(string) recomprised = strings.Replace(recomprised, fmt.Sprintf("{%s}", field), val, -1) @@ -366,9 +372,19 @@ func GetAPIRequestDataWithSlice(d *schema.ResourceData, fields []string) map[str // GetAPIRequestDataWithSliceOk to pass to Vault from schema.ResourceData. // Only field values that are set in schema.ResourceData will be returned func GetAPIRequestDataWithSliceOk(d *schema.ResourceData, fields []string) map[string]interface{} { + return getAPIRequestDataWithSlice(d, GetAPIRequestValueOk, fields) +} + +// GetAPIRequestDataWithSliceOkExists to pass to Vault from schema.ResourceData. +// Only field values that are set in schema.ResourceData will be returned +func GetAPIRequestDataWithSliceOkExists(d *schema.ResourceData, fields []string) map[string]interface{} { + return getAPIRequestDataWithSlice(d, GetAPIRequestValueOkExists, fields) +} + +func getAPIRequestDataWithSlice(d *schema.ResourceData, f VaultAPIValueGetter, fields []string) map[string]interface{} { data := make(map[string]interface{}) for _, k := range fields { - if v, ok := getAPIRequestValueOk(d, k); ok { + if v, ok := f(d, k); ok { data[k] = v } } @@ -389,15 +405,30 @@ func getAPIValue(i interface{}) interface{} { } } -func getAPIRequestValueOk(d *schema.ResourceData, k string) (interface{}, bool) { +// GetAPIRequestValueOk returns the Vault API compatible value from *schema.ResourceData for provided key, +// along with boolean representing keys existence in the resource data. +// This is equivalent to calling the schema.ResourceData's GetOk() method. +func GetAPIRequestValueOk(d *schema.ResourceData, k string) (interface{}, bool) { sv, ok := d.GetOk(k) - if !ok { - return nil, ok - } + return getAPIValue(sv), ok +} +// GetAPIRequestValueOkExists returns the Vault API compatible value from *schema.ResourceData for provided key, +// along with boolean representing keys existence in the resource data. +// This is equivalent to calling the schema.ResourceData's deprecated GetOkExists() method. +func GetAPIRequestValueOkExists(d *schema.ResourceData, k string) (interface{}, bool) { + sv, ok := d.GetOkExists(k) return getAPIValue(sv), ok } +// GetAPIRequestValue returns the value from *schema.ResourceData for provide key. +// The existence boolean is always true, so it should be ignored, +// this is done in order to satisfy the VaultAPIValueGetter type. +// This is equivalent to calling the schema.ResourceData's Get() method. +func GetAPIRequestValue(d *schema.ResourceData, k string) (interface{}, bool) { + return getAPIValue(d.Get(k)), true +} + func Remount(d *schema.ResourceData, client *api.Client, mountField string, isAuthMount bool) (string, error) { ret := d.Get(mountField).(string) @@ -407,7 +438,6 @@ func Remount(d *schema.ResourceData, client *api.Client, mountField string, isAu o, n := d.GetChange(mountField) oldPath := o.(string) newPath := n.(string) - if isAuthMount { oldPath = "auth/" + oldPath newPath = "auth/" + newPath diff --git a/util/util_test.go b/util/util_test.go index 236886ab5..7e05d5194 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -433,6 +433,215 @@ func TestGetAPIRequestDataWithSlice(t *testing.T) { } } +func TestGetAPIRequestDataWithSliceOk(t *testing.T) { + tests := []struct { + name string + d map[string]*schema.Schema + s []string + sm map[string]interface{} + want map[string]interface{} + }{ + { + name: "basic-default", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + }, + s: []string{"name"}, + sm: map[string]interface{}{ + "name": "bob", + }, + want: map[string]interface{}{ + "name": "bob", + }, + }, + { + name: "set", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + "parts": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + s: []string{ + "name", + "parts", + }, + sm: map[string]interface{}{ + "name": "alice", + "parts": []interface{}{ + "bolt", + }, + }, + want: map[string]interface{}{ + "name": "alice", + "parts": []interface{}{ + "bolt", + }, + }, + }, + { + name: "parts-field-not-configured", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + }, + s: []string{ + "name", + "parts", + }, + sm: map[string]interface{}{ + "name": "alice", + }, + want: map[string]interface{}{ + "name": "alice", + }, + }, + { + name: "zero-value-int-field", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + "enabled": { + Type: schema.TypeInt, + }, + }, + s: []string{ + "name", + "enabled", + }, + sm: map[string]interface{}{ + "name": "alice", + "enabled": 0, + }, + want: map[string]interface{}{ + "name": "alice", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := schema.TestResourceDataRaw(t, tt.d, tt.sm) + if got := GetAPIRequestDataWithSliceOk(r, tt.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAPIRequestDataWithSliceOk() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetAPIRequestDataWithSliceOkExists(t *testing.T) { + tests := []struct { + name string + d map[string]*schema.Schema + s []string + sm map[string]interface{} + want map[string]interface{} + }{ + { + name: "basic-default", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + }, + s: []string{"name"}, + sm: map[string]interface{}{ + "name": "bob", + }, + want: map[string]interface{}{ + "name": "bob", + }, + }, + { + name: "set", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + "parts": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + s: []string{ + "name", + "parts", + }, + sm: map[string]interface{}{ + "name": "alice", + "parts": []interface{}{ + "bolt", + }, + }, + want: map[string]interface{}{ + "name": "alice", + "parts": []interface{}{ + "bolt", + }, + }, + }, + { + name: "parts-field-not-configured", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + }, + s: []string{ + "name", + "parts", + }, + sm: map[string]interface{}{ + "name": "alice", + }, + want: map[string]interface{}{ + "name": "alice", + }, + }, + { + name: "zero-value-int-field", + d: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + "enabled": { + Type: schema.TypeInt, + }, + }, + s: []string{ + "name", + "enabled", + }, + sm: map[string]interface{}{ + "name": "alice", + "enabled": 0, + }, + want: map[string]interface{}{ + "name": "alice", + "enabled": 0, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := schema.TestResourceDataRaw(t, tt.d, tt.sm) + if got := GetAPIRequestDataWithSliceOkExists(r, tt.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAPIRequestDataWithSliceOkExists() = %v, want %v", got, tt.want) + } + }) + } +} + func TestCalculateConflictsWith(t *testing.T) { tests := []struct { name string diff --git a/vault/resource_identity_mfa_duo_test.go b/vault/resource_identity_mfa_duo_test.go index 63ac5ab85..8cfc7e501 100644 --- a/vault/resource_identity_mfa_duo_test.go +++ b/vault/resource_identity_mfa_duo_test.go @@ -61,6 +61,7 @@ resource "%s" "test" { integration_key = "int-key-2" api_hostname = "foo.baz" push_info = "push-info-2" + username_format = "" } `, mfa.ResourceNameDuo), Check: resource.ComposeAggregateTestCheckFunc( diff --git a/vault/resource_identity_mfa_login_enforcement_test.go b/vault/resource_identity_mfa_login_enforcement_test.go index e73f49c7e..39cc3b309 100644 --- a/vault/resource_identity_mfa_login_enforcement_test.go +++ b/vault/resource_identity_mfa_login_enforcement_test.go @@ -24,6 +24,8 @@ func TestIdentityMFALoginEnforcement(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, consts.FieldUUID), resource.TestCheckResourceAttr(resourceName, consts.FieldNamespaceID, "root"), resource.TestCheckResourceAttr(resourceName, consts.FieldName, name), + resource.TestCheckResourceAttr(resourceName, consts.FieldAuthMethodTypes+".#", "1"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAuthMethodTypes+".0", "token"), } importTestStep := testutil.GetImportTestStep(resourceName, false, nil, consts.FieldIntegrationKey, consts.FieldSecretKey) @@ -34,7 +36,7 @@ func TestIdentityMFALoginEnforcement(t *testing.T) { { Config: getTestMFAEnforcementConfig(name), Check: resource.ComposeAggregateTestCheckFunc( - append(checksCommon)..., + checksCommon..., ), }, importTestStep, @@ -57,6 +59,9 @@ resource "vault_identity_mfa_login_enforcement" "test" { mfa_method_ids = [ vault_identity_mfa_duo.test.method_id, ] + auth_method_types = [ + "token" + ] } `, name) diff --git a/vault/resource_identity_mfa_totp_test.go b/vault/resource_identity_mfa_totp_test.go index c63656a2c..f02c34371 100644 --- a/vault/resource_identity_mfa_totp_test.go +++ b/vault/resource_identity_mfa_totp_test.go @@ -35,7 +35,7 @@ func TestIdentityMFATOTP(t *testing.T) { { Config: fmt.Sprintf(` resource "%s" "test" { - issuer = "issuer1" + issuer = "issuer1" } `, mfa.ResourceNameTOTP), Check: resource.ComposeAggregateTestCheckFunc( @@ -43,7 +43,7 @@ resource "%s" "test" { resource.TestCheckResourceAttr(resourceName, consts.FieldIssuer, "issuer1"), resource.TestCheckResourceAttr(resourceName, consts.FieldPeriod, "30"), resource.TestCheckResourceAttr(resourceName, consts.FieldKeySize, "20"), - resource.TestCheckResourceAttr(resourceName, consts.FieldQRSize, "0"), + resource.TestCheckResourceAttr(resourceName, consts.FieldQRSize, "200"), resource.TestCheckResourceAttr(resourceName, consts.FieldAlgorithm, "SHA256"), resource.TestCheckResourceAttr(resourceName, consts.FieldDigits, "6"), resource.TestCheckResourceAttr(resourceName, consts.FieldSkew, "1"), @@ -61,6 +61,32 @@ resource "%s" "test" { digits = 8 skew = 0 max_validation_attempts = 10 + qr_size = 300 +} +`, mfa.ResourceNameTOTP), + Check: resource.ComposeAggregateTestCheckFunc( + append(checksCommon, + resource.TestCheckResourceAttr(resourceName, consts.FieldIssuer, "issuer2"), + resource.TestCheckResourceAttr(resourceName, consts.FieldPeriod, "60"), + resource.TestCheckResourceAttr(resourceName, consts.FieldKeySize, "30"), + resource.TestCheckResourceAttr(resourceName, consts.FieldQRSize, "300"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAlgorithm, "SHA512"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDigits, "8"), + resource.TestCheckResourceAttr(resourceName, consts.FieldSkew, "0"), + resource.TestCheckResourceAttr(resourceName, consts.FieldMaxValidationAttempts, "10"), + )...), + }, + { + Config: fmt.Sprintf(` +resource "%s" "test" { + issuer = "issuer2" + period = 60 + key_size = 30 + algorithm = "SHA512" + digits = 8 + skew = 0 + max_validation_attempts = 10 + qr_size = 0 } `, mfa.ResourceNameTOTP), Check: resource.ComposeAggregateTestCheckFunc(