Implement password Option and re-implement editing

Editing now shows all the options for the fs and asks one at a time
whether they should be changed.
This commit is contained in:
Nick Craig-Wood 2016-08-14 17:16:06 +01:00
parent 6a4e424630
commit b1de4c8cba
3 changed files with 93 additions and 58 deletions

View File

@ -493,7 +493,7 @@ func loadConfigFile() (*goconfig.ConfigFile, error) {
var out []byte
for {
if len(configKey) == 0 && envpw != "" {
err := setPassword(envpw)
err := setConfigPassword(envpw)
if err != nil {
fmt.Println("Using RCLONE_CONFIG_PASS returned:", err)
envpw = ""
@ -505,7 +505,7 @@ func loadConfigFile() (*goconfig.ConfigFile, error) {
if !*AskPassword {
return nil, errors.New("unable to decrypt configuration and not allowed to ask for password - set RCLONE_CONFIG_PASS to your configuration password")
}
getPassword("Enter configuration password:")
getConfigPassword("Enter configuration password:")
}
// Nonce is first 24 bytes of the ciphertext
@ -529,16 +529,57 @@ func loadConfigFile() (*goconfig.ConfigFile, error) {
return goconfig.LoadFromReader(bytes.NewBuffer(out))
}
// getPassword will query the user for a password the
// checkPassword normalises and validates the password
func checkPassword(password string) (string, error) {
if !utf8.ValidString(password) {
return "", errors.New("password contains invalid utf8 characters")
}
// Remove leading+trailing whitespace
password = strings.TrimSpace(password)
// Normalize to reduce weird variations.
password = norm.NFKC.String(password)
if len(password) == 0 {
return "", errors.New("no characters in password")
}
return password, nil
}
// GetPassword asks the user for a password with the prompt given.
func GetPassword(prompt string) string {
fmt.Println(prompt)
for {
fmt.Print("password:")
password := ReadPassword()
password, err := checkPassword(password)
if err == nil {
return password
}
fmt.Printf("Bad password: %v", err)
}
}
// ChangePassword will query the user twice for the named password. If
// the same password is entered it is returned.
func ChangePassword(name string) string {
for {
a := GetPassword(fmt.Sprintf("Enter %s password:", name))
b := GetPassword(fmt.Sprintf("Confirm %s password:", name))
if a == b {
return a
}
fmt.Println("Passwords do not match!")
}
}
// getConfigPassword will query the user for a password the
// first time it is required.
func getPassword(q string) {
func getConfigPassword(q string) {
if len(configKey) != 0 {
return
}
for {
fmt.Println(q)
fmt.Print("password:")
err := setPassword(ReadPassword())
password := GetPassword(q)
err := setConfigPassword(password)
if err == nil {
return
}
@ -546,24 +587,17 @@ func getPassword(q string) {
}
}
// setPassword will set the configKey to the hash of
// setConfigPassword will set the configKey to the hash of
// the password. If the length of the password is
// zero after trimming+normalization, an error is returned.
func setPassword(password string) error {
if !utf8.ValidString(password) {
return errors.New("password contains invalid utf8 characters")
}
// Remove leading+trailing whitespace
password = strings.TrimSpace(password)
// Normalize to reduce weird variations.
password = norm.NFKC.String(password)
if len(password) == 0 {
return errors.New("no characters in password")
func setConfigPassword(password string) error {
password, err := checkPassword(password)
if err != nil {
return err
}
// Create SHA256 has of the password
sha := sha256.New()
_, err := sha.Write([]byte("[" + password + "][rclone-config]"))
_, err = sha.Write([]byte("[" + password + "][rclone-config]"))
if err != nil {
return err
}
@ -571,6 +605,17 @@ func setPassword(password string) error {
return nil
}
// changeConfigPassword will query the user twice
// for a password. If the same password is entered
// twice the key is updated.
func changeConfigPassword() {
err := setConfigPassword(ChangePassword("NEW configuration"))
if err != nil {
fmt.Printf("Failed to set config password: %v\n", err)
return
}
}
// SaveConfig saves configuration file.
// if configKey has been set, the file will be encrypted.
func SaveConfig() {
@ -808,6 +853,9 @@ func RemoteConfig(name string) {
// ChooseOption asks the user to choose an option
func ChooseOption(o *Option) string {
fmt.Println(o.Help)
if o.IsPassword {
return MustObscure(ChangePassword("the"))
}
if len(o.Examples) > 0 {
var values []string
var help []string
@ -854,20 +902,21 @@ func NewRemote(name string) {
SaveConfig()
return
}
EditRemote(name)
EditRemote(fs, name)
}
// EditRemote gets the user to edit a remote
func EditRemote(name string) {
func EditRemote(fs *RegInfo, name string) {
ShowRemote(name)
fmt.Printf("Edit remote\n")
for {
for _, key := range ConfigFile.GetKeyList(name) {
for _, option := range fs.Options {
key := option.Name
value := ConfigFile.MustValue(name, key)
fmt.Printf("Press enter to accept current value, or type in a new one\n")
fmt.Printf("%s = %s>", key, value)
newValue := ReadLine()
if newValue != "" {
fmt.Printf("Value %q = %q\n", key, value)
fmt.Printf("Edit? (y/n)>\n")
if Confirm() {
newValue := ChooseOption(&option)
ConfigFile.SetValue(name, key, newValue)
}
}
@ -901,7 +950,11 @@ func EditConfig() {
switch i := Command(what); i {
case 'e':
name := ChooseRemote()
EditRemote(name)
fs, err := Find(ConfigFile.MustValue(name, "type"))
if err != nil {
log.Fatalf("Failed to find fs: %v", err)
}
EditRemote(fs, name)
case 'n':
nameLoop:
for {
@ -941,7 +994,7 @@ func SetPassword() {
what := []string{"cChange Password", "uUnencrypt configuration", "qQuit to main menu"}
switch i := Command(what); i {
case 'c':
changePassword()
changeConfigPassword()
SaveConfig()
fmt.Println("Password changed")
continue
@ -959,7 +1012,7 @@ func SetPassword() {
what := []string{"aAdd Password", "qQuit to main menu"}
switch i := Command(what); i {
case 'a':
changePassword()
changeConfigPassword()
SaveConfig()
fmt.Println("Password set")
continue
@ -970,25 +1023,6 @@ func SetPassword() {
}
}
// changePassword will query the user twice
// for a password. If the same password is entered
// twice the key is updated.
func changePassword() {
for {
configKey = nil
getPassword("Enter NEW configuration password:")
a := configKey
// re-enter password
configKey = nil
getPassword("Confirm NEW password:")
b := configKey
if bytes.Equal(a, b) {
return
}
fmt.Println("Passwords does not match!")
}
}
// Authorize is for remote authorization of headless machines.
//
// It expects 1 or 3 arguments

View File

@ -162,7 +162,7 @@ func TestConfigLoadEncrypted(t *testing.T) {
}()
// Set correct password
err = setPassword("asdf")
err = setConfigPassword("asdf")
require.NoError(t, err)
c, err := loadConfigFile()
require.NoError(t, err)
@ -208,11 +208,11 @@ func TestPassword(t *testing.T) {
}()
var err error
// Empty password should give error
err = setPassword(" \t ")
err = setConfigPassword(" \t ")
require.Error(t, err)
// Test invalid utf8 sequence
err = setPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc")
err = setConfigPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc")
require.Error(t, err)
// Simple check of wrong passwords
@ -230,11 +230,11 @@ func TestPassword(t *testing.T) {
}
func hashedKeyCompare(t *testing.T, a, b string, shouldMatch bool) {
err := setPassword(a)
err := setConfigPassword(a)
require.NoError(t, err)
k1 := configKey
err = setPassword(b)
err = setConfigPassword(b)
require.NoError(t, err)
k2 := configKey

View File

@ -66,10 +66,11 @@ type RegInfo struct {
// Option is describes an option for the config wizard
type Option struct {
Name string
Help string
Optional bool
Examples OptionExamples
Name string
Help string
Optional bool
IsPassword bool
Examples OptionExamples
}
// OptionExamples is a slice of examples