Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure that the qr_size can be properly configured. #1750

Merged
merged 5 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/identity/mfa/duo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion internal/identity/mfa/login_enforcement.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
106 changes: 75 additions & 31 deletions internal/identity/mfa/mfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 == "" {
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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,
},
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion internal/identity/mfa/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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.
Expand All @@ -54,5 +56,7 @@ func GetOKTASchemaResource() (*schema.Resource, error) {
return nil, err
}

config.setAPIValueGetter(consts.FieldUsernameFormat, util.GetAPIRequestValue)

return getMethodSchemaResource(oktaSchemaMap, config), nil
}
3 changes: 2 additions & 1 deletion internal/identity/mfa/ping_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -73,7 +74,7 @@ func GetPingIDSchemaResource() (*schema.Resource, error) {
consts.FieldAdminURL,
consts.FieldAuthenticatorURL,
consts.FieldOrgAlias,
}, nil)
}, nil, nil)
if err != nil {
return nil, err
}
Expand Down
8 changes: 7 additions & 1 deletion internal/identity/mfa/totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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
}
46 changes: 38 additions & 8 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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)

Expand All @@ -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
Expand Down
Loading