chore: refactor password code for StdIO (#139)

This commit is contained in:
Dane Strandboge
2021-06-22 15:34:38 -05:00
committed by GitHub
parent f80b91730d
commit a3408e031a
9 changed files with 52 additions and 45 deletions

View File

@ -8,8 +8,6 @@ import (
"github.com/influxdata/influx-cli/v2/pkg/stdio" "github.com/influxdata/influx-cli/v2/pkg/stdio"
) )
const MinPasswordLen = 8
// CLI is a container for common functionality used to execute commands. // CLI is a container for common functionality used to execute commands.
type CLI struct { type CLI struct {
StdIO stdio.StdIO StdIO stdio.StdIO

View File

@ -13,6 +13,7 @@ import (
"github.com/influxdata/influx-cli/v2/clients/bucket" "github.com/influxdata/influx-cli/v2/clients/bucket"
"github.com/influxdata/influx-cli/v2/config" "github.com/influxdata/influx-cli/v2/config"
"github.com/influxdata/influx-cli/v2/internal/duration" "github.com/influxdata/influx-cli/v2/internal/duration"
"github.com/influxdata/influx-cli/v2/pkg/stdio"
) )
var ( var (
@ -121,7 +122,7 @@ func (c Client) validateNoNameCollision(configName string) error {
// Unless the 'force' parameter is set, the user will be prompted to enter any missing information // Unless the 'force' parameter is set, the user will be prompted to enter any missing information
// and to confirm the final request parameters. // and to confirm the final request parameters.
func (c Client) onboardingRequest(params *Params) (req api.OnboardingRequest, err error) { func (c Client) onboardingRequest(params *Params) (req api.OnboardingRequest, err error) {
if (params.Force || params.Password != "") && len(params.Password) < clients.MinPasswordLen { if (params.Force || params.Password != "") && len(params.Password) < stdio.MinPasswordLen {
return req, clients.ErrPasswordIsTooShort return req, clients.ErrPasswordIsTooShort
} }
@ -164,24 +165,11 @@ func (c Client) onboardingRequest(params *Params) (req api.OnboardingRequest, er
} }
} }
if params.Password == "" { if params.Password == "" {
for { pass, err := c.StdIO.GetPassword("Please type your password")
pass1, err := c.StdIO.GetSecret("Please type your password", clients.MinPasswordLen) if err != nil {
if err != nil { return req, err
return req, err
}
// Don't bother with the length check the 2nd time, since we check equality to pass1.
pass2, err := c.StdIO.GetSecret("Please type your password again", 0)
if err != nil {
return req, err
}
if pass1 == pass2 {
req.Password = &pass1
break
}
if err := c.StdIO.Error("Passwords do not match"); err != nil {
return req, err
}
} }
req.Password = &pass
} }
if params.Org == "" { if params.Org == "" {
req.Org, err = c.StdIO.GetStringInput("Please type your primary organization name", "") req.Org, err = c.StdIO.GetStringInput("Please type your primary organization name", "")

View File

@ -215,8 +215,7 @@ func Test_SetupSuccessInteractive(t *testing.T) {
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes() stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
stdio.EXPECT().Banner(gomock.Any()) stdio.EXPECT().Banner(gomock.Any())
stdio.EXPECT().GetStringInput(gomock.Eq("Please type your primary username"), gomock.Any()).Return(username, nil) stdio.EXPECT().GetStringInput(gomock.Eq("Please type your primary username"), gomock.Any()).Return(username, nil)
stdio.EXPECT().GetSecret(gomock.Eq("Please type your password"), gomock.Any()).Return(password, nil) stdio.EXPECT().GetPassword(gomock.Eq("Please type your password")).Return(password, nil)
stdio.EXPECT().GetSecret(gomock.Eq("Please type your password again"), gomock.Any()).Return(password, nil)
stdio.EXPECT().GetStringInput(gomock.Eq("Please type your primary organization name"), gomock.Any()).Return(org, nil) stdio.EXPECT().GetStringInput(gomock.Eq("Please type your primary organization name"), gomock.Any()).Return(org, nil)
stdio.EXPECT().GetStringInput("Please type your primary bucket name", gomock.Any()).Return(bucket, nil) stdio.EXPECT().GetStringInput("Please type your primary bucket name", gomock.Any()).Return(bucket, nil)
stdio.EXPECT().GetStringInput("Please type your retention period in hours, or 0 for infinite", gomock.Any()).Return(strconv.Itoa(retentionHrs), nil) stdio.EXPECT().GetStringInput("Please type your retention period in hours, or 0 for infinite", gomock.Any()).Return(strconv.Itoa(retentionHrs), nil)

View File

@ -8,6 +8,7 @@ import (
"github.com/influxdata/influx-cli/v2/api" "github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/clients" "github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/pkg/influxid" "github.com/influxdata/influx-cli/v2/pkg/influxid"
"github.com/influxdata/influx-cli/v2/pkg/stdio"
) )
type Client struct { type Client struct {
@ -49,7 +50,7 @@ func getOrgID(ctx context.Context, params *clients.OrgParams, c clients.CLI, org
} }
func (c Client) Create(ctx context.Context, params *CreateParams) error { func (c Client) Create(ctx context.Context, params *CreateParams) error {
if params.Password != "" && len(params.Password) < clients.MinPasswordLen { if params.Password != "" && len(params.Password) < stdio.MinPasswordLen {
return clients.ErrPasswordIsTooShort return clients.ErrPasswordIsTooShort
} }
@ -159,31 +160,16 @@ func (c Client) SetPassword(ctx context.Context, params *SetPasswordParams) erro
id = (*users.Users)[0].GetId() id = (*users.Users)[0].GetId()
} }
var password string password, err := c.StdIO.GetPassword(fmt.Sprintf("Please type new password for %q", displayName))
for { if err != nil {
pass1, err := c.StdIO.GetSecret(fmt.Sprintf("Please type new password for %q", displayName), clients.MinPasswordLen) return err
if err != nil {
return err
}
// Don't bother with the length check the 2nd time, since we check equality to pass1.
pass2, err := c.StdIO.GetSecret("Please type new password again", 0)
if err != nil {
return err
}
if pass1 == pass2 {
password = pass1
break
}
if err := c.StdIO.Error("Passwords do not match"); err != nil {
return err
}
} }
body := api.PasswordResetBody{Password: password} body := api.PasswordResetBody{Password: password}
if err := c.PostUsersIDPassword(ctx, id).PasswordResetBody(body).Execute(); err != nil { if err := c.PostUsersIDPassword(ctx, id).PasswordResetBody(body).Execute(); err != nil {
return fmt.Errorf("failed to set password for user %q: %w", params.Id.String(), err) return fmt.Errorf("failed to set password for user %q: %w", params.Id.String(), err)
} }
_, err := c.StdIO.Write([]byte(fmt.Sprintf("Successfully updated password for user %q\n", displayName))) _, err = c.StdIO.Write([]byte(fmt.Sprintf("Successfully updated password for user %q\n", displayName)))
return err return err
} }

View File

@ -573,7 +573,7 @@ func TestClient_SetPassword(t *testing.T) {
stdio := mock.NewMockStdIO(ctrl) stdio := mock.NewMockStdIO(ctrl)
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(stdout.Write).AnyTimes() stdio.EXPECT().Write(gomock.Any()).DoAndReturn(stdout.Write).AnyTimes()
if !tc.noExpectAsk { if !tc.noExpectAsk {
stdio.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return("mypassword", nil).Times(2) stdio.EXPECT().GetPassword(gomock.Any()).Return("mypassword", nil)
} }
cli := user.Client{CLI: clients.CLI{StdIO: stdio}, UsersApi: userApi} cli := user.Client{CLI: clients.CLI{StdIO: stdio}, UsersApi: userApi}

View File

@ -75,6 +75,21 @@ func (mr *MockStdIOMockRecorder) GetConfirm(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfirm", reflect.TypeOf((*MockStdIO)(nil).GetConfirm), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfirm", reflect.TypeOf((*MockStdIO)(nil).GetConfirm), arg0)
} }
// GetPassword mocks base method.
func (m *MockStdIO) GetPassword(arg0 string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPassword", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPassword indicates an expected call of GetPassword.
func (mr *MockStdIOMockRecorder) GetPassword(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPassword", reflect.TypeOf((*MockStdIO)(nil).GetPassword), arg0)
}
// GetSecret mocks base method. // GetSecret mocks base method.
func (m *MockStdIO) GetSecret(arg0 string, arg1 int) (string, error) { func (m *MockStdIO) GetSecret(arg0 string, arg1 int) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -67,7 +67,7 @@ func (t *terminalStdio) GetStringInput(prompt, defaultValue string) (input strin
return return
} }
// GetSecret prompts the user for a password. // GetSecret prompts the user for a secret.
func (t *terminalStdio) GetSecret(prompt string, minLen int) (password string, err error) { func (t *terminalStdio) GetSecret(prompt string, minLen int) (password string, err error) {
question := survey.Password{Message: prompt} question := survey.Password{Message: prompt}
opts := []survey.AskOpt{survey.WithStdio(t.Stdin, t.Stdout, t.Stderr)} opts := []survey.AskOpt{survey.WithStdio(t.Stdin, t.Stdout, t.Stderr)}
@ -79,6 +79,24 @@ func (t *terminalStdio) GetSecret(prompt string, minLen int) (password string, e
return return
} }
// GetPassword prompts the user for a secret twice, and inputs must match.
// Uses stdio.MinPasswordLen as the minimum input length
func (t *terminalStdio) GetPassword(prompt string) (string, error) {
pass1, err := t.GetSecret(prompt, MinPasswordLen)
if err != nil {
return "", err
}
// Don't bother with the length check the 2nd time, since we check equality to pass1.
pass2, err := t.GetSecret(prompt+" again", 0)
if err != nil {
return "", err
}
if pass1 == pass2 {
return pass1, nil
}
return "", t.Error("Passwords do not match")
}
// GetConfirm asks the user for a y/n answer to a prompt. // GetConfirm asks the user for a y/n answer to a prompt.
func (t *terminalStdio) GetConfirm(prompt string) (answer bool) { func (t *terminalStdio) GetConfirm(prompt string) (answer bool) {
question := survey.Confirm{ question := survey.Confirm{

View File

@ -2,6 +2,8 @@ package stdio
import "io" import "io"
const MinPasswordLen = 8
type StdIO interface { type StdIO interface {
io.Writer io.Writer
WriteErr(p []byte) (n int, err error) WriteErr(p []byte) (n int, err error)
@ -9,5 +11,6 @@ type StdIO interface {
Error(message string) error Error(message string) error
GetStringInput(prompt, defaultValue string) (string, error) GetStringInput(prompt, defaultValue string) (string, error)
GetSecret(prompt string, minLen int) (string, error) GetSecret(prompt string, minLen int) (string, error)
GetPassword(prompt string) (string, error)
GetConfirm(prompt string) bool GetConfirm(prompt string) bool
} }