github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/rclone/rclone/fs"
    11  	"github.com/rclone/rclone/fs/config/obscure"
    12  	"github.com/rclone/rclone/fs/rc"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func testConfigFile(t *testing.T, configFileName string) func() {
    18  	configKey = nil // reset password
    19  	_ = os.Unsetenv("_RCLONE_CONFIG_KEY_FILE")
    20  	_ = os.Unsetenv("RCLONE_CONFIG_PASS")
    21  	// create temp config file
    22  	tempFile, err := ioutil.TempFile("", configFileName)
    23  	assert.NoError(t, err)
    24  	path := tempFile.Name()
    25  	assert.NoError(t, tempFile.Close())
    26  
    27  	// temporarily adapt configuration
    28  	oldOsStdout := os.Stdout
    29  	oldConfigPath := ConfigPath
    30  	oldConfig := fs.Config
    31  	oldConfigFile := configFile
    32  	oldReadLine := ReadLine
    33  	oldPassword := Password
    34  	os.Stdout = nil
    35  	ConfigPath = path
    36  	fs.Config = &fs.ConfigInfo{}
    37  	configFile = nil
    38  
    39  	LoadConfig()
    40  	assert.Equal(t, []string{}, getConfigData().GetSectionList())
    41  
    42  	// Fake a remote
    43  	fs.Register(&fs.RegInfo{
    44  		Name: "config_test_remote",
    45  		Options: fs.Options{
    46  			{
    47  				Name:       "bool",
    48  				Default:    false,
    49  				IsPassword: false,
    50  			},
    51  			{
    52  				Name:       "pass",
    53  				Default:    "",
    54  				IsPassword: true,
    55  			},
    56  		},
    57  	})
    58  
    59  	// Undo the above
    60  	return func() {
    61  		err := os.Remove(path)
    62  		assert.NoError(t, err)
    63  
    64  		os.Stdout = oldOsStdout
    65  		ConfigPath = oldConfigPath
    66  		ReadLine = oldReadLine
    67  		Password = oldPassword
    68  		fs.Config = oldConfig
    69  		configFile = oldConfigFile
    70  
    71  		_ = os.Unsetenv("_RCLONE_CONFIG_KEY_FILE")
    72  		_ = os.Unsetenv("RCLONE_CONFIG_PASS")
    73  	}
    74  }
    75  
    76  // makeReadLine makes a simple readLine which returns a fixed list of
    77  // strings
    78  func makeReadLine(answers []string) func() string {
    79  	i := 0
    80  	return func() string {
    81  		i = i + 1
    82  		return answers[i-1]
    83  	}
    84  }
    85  
    86  func TestCRUD(t *testing.T) {
    87  	defer testConfigFile(t, "crud.conf")()
    88  
    89  	// script for creating remote
    90  	ReadLine = makeReadLine([]string{
    91  		"config_test_remote", // type
    92  		"true",               // bool value
    93  		"y",                  // type my own password
    94  		"secret",             // password
    95  		"secret",             // repeat
    96  		"y",                  // looks good, save
    97  	})
    98  	NewRemote("test")
    99  
   100  	assert.Equal(t, []string{"test"}, configFile.GetSectionList())
   101  	assert.Equal(t, "config_test_remote", FileGet("test", "type"))
   102  	assert.Equal(t, "true", FileGet("test", "bool"))
   103  	assert.Equal(t, "secret", obscure.MustReveal(FileGet("test", "pass")))
   104  
   105  	// normal rename, test → asdf
   106  	ReadLine = makeReadLine([]string{
   107  		"asdf",
   108  		"asdf",
   109  		"asdf",
   110  	})
   111  	RenameRemote("test")
   112  
   113  	assert.Equal(t, []string{"asdf"}, configFile.GetSectionList())
   114  	assert.Equal(t, "config_test_remote", FileGet("asdf", "type"))
   115  	assert.Equal(t, "true", FileGet("asdf", "bool"))
   116  	assert.Equal(t, "secret", obscure.MustReveal(FileGet("asdf", "pass")))
   117  
   118  	// delete remote
   119  	DeleteRemote("asdf")
   120  	assert.Equal(t, []string{}, configFile.GetSectionList())
   121  }
   122  
   123  func TestChooseOption(t *testing.T) {
   124  	defer testConfigFile(t, "crud.conf")()
   125  
   126  	// script for creating remote
   127  	ReadLine = makeReadLine([]string{
   128  		"config_test_remote", // type
   129  		"false",              // bool value
   130  		"x",                  // bad choice
   131  		"g",                  // generate password
   132  		"1024",               // very big
   133  		"y",                  // password OK
   134  		"y",                  // looks good, save
   135  	})
   136  	Password = func(bits int) (string, error) {
   137  		assert.Equal(t, 1024, bits)
   138  		return "not very random password", nil
   139  	}
   140  	NewRemote("test")
   141  
   142  	assert.Equal(t, "false", FileGet("test", "bool"))
   143  	assert.Equal(t, "not very random password", obscure.MustReveal(FileGet("test", "pass")))
   144  
   145  	// script for creating remote
   146  	ReadLine = makeReadLine([]string{
   147  		"config_test_remote", // type
   148  		"true",               // bool value
   149  		"n",                  // not required
   150  		"y",                  // looks good, save
   151  	})
   152  	NewRemote("test")
   153  
   154  	assert.Equal(t, "true", FileGet("test", "bool"))
   155  	assert.Equal(t, "", FileGet("test", "pass"))
   156  }
   157  
   158  func TestNewRemoteName(t *testing.T) {
   159  	defer testConfigFile(t, "crud.conf")()
   160  
   161  	// script for creating remote
   162  	ReadLine = makeReadLine([]string{
   163  		"config_test_remote", // type
   164  		"true",               // bool value
   165  		"n",                  // not required
   166  		"y",                  // looks good, save
   167  	})
   168  	NewRemote("test")
   169  
   170  	ReadLine = makeReadLine([]string{
   171  		"test",           // already exists
   172  		"",               // empty string not allowed
   173  		"bad@characters", // bad characters
   174  		"newname",        // OK
   175  	})
   176  
   177  	assert.Equal(t, "newname", NewRemoteName())
   178  }
   179  
   180  func TestCreateUpatePasswordRemote(t *testing.T) {
   181  	defer testConfigFile(t, "update.conf")()
   182  
   183  	for _, doObscure := range []bool{false, true} {
   184  		for _, noObscure := range []bool{false, true} {
   185  			if doObscure && noObscure {
   186  				break
   187  			}
   188  			t.Run(fmt.Sprintf("doObscure=%v,noObscure=%v", doObscure, noObscure), func(t *testing.T) {
   189  				require.NoError(t, CreateRemote("test2", "config_test_remote", rc.Params{
   190  					"bool": true,
   191  					"pass": "potato",
   192  				}, doObscure, noObscure))
   193  
   194  				assert.Equal(t, []string{"test2"}, configFile.GetSectionList())
   195  				assert.Equal(t, "config_test_remote", FileGet("test2", "type"))
   196  				assert.Equal(t, "true", FileGet("test2", "bool"))
   197  				gotPw := FileGet("test2", "pass")
   198  				if !noObscure {
   199  					gotPw = obscure.MustReveal(gotPw)
   200  				}
   201  				assert.Equal(t, "potato", gotPw)
   202  
   203  				wantPw := obscure.MustObscure("potato2")
   204  				require.NoError(t, UpdateRemote("test2", rc.Params{
   205  					"bool":  false,
   206  					"pass":  wantPw,
   207  					"spare": "spare",
   208  				}, doObscure, noObscure))
   209  
   210  				assert.Equal(t, []string{"test2"}, configFile.GetSectionList())
   211  				assert.Equal(t, "config_test_remote", FileGet("test2", "type"))
   212  				assert.Equal(t, "false", FileGet("test2", "bool"))
   213  				gotPw = FileGet("test2", "pass")
   214  				if doObscure {
   215  					gotPw = obscure.MustReveal(gotPw)
   216  				}
   217  				assert.Equal(t, wantPw, gotPw)
   218  
   219  				require.NoError(t, PasswordRemote("test2", rc.Params{
   220  					"pass": "potato3",
   221  				}))
   222  
   223  				assert.Equal(t, []string{"test2"}, configFile.GetSectionList())
   224  				assert.Equal(t, "config_test_remote", FileGet("test2", "type"))
   225  				assert.Equal(t, "false", FileGet("test2", "bool"))
   226  				assert.Equal(t, "potato3", obscure.MustReveal(FileGet("test2", "pass")))
   227  			})
   228  		}
   229  	}
   230  
   231  }
   232  
   233  // Test some error cases
   234  func TestReveal(t *testing.T) {
   235  	for _, test := range []struct {
   236  		in      string
   237  		wantErr string
   238  	}{
   239  		{"YmJiYmJiYmJiYmJiYmJiYp*gcEWbAw", "base64 decode failed when revealing password - is it obscured?: illegal base64 data at input byte 22"},
   240  		{"aGVsbG8", "input too short when revealing password - is it obscured?"},
   241  		{"", "input too short when revealing password - is it obscured?"},
   242  	} {
   243  		gotString, gotErr := obscure.Reveal(test.in)
   244  		assert.Equal(t, "", gotString)
   245  		assert.Equal(t, test.wantErr, gotErr.Error())
   246  	}
   247  }
   248  
   249  func TestConfigLoad(t *testing.T) {
   250  	oldConfigPath := ConfigPath
   251  	ConfigPath = "./testdata/plain.conf"
   252  	defer func() {
   253  		ConfigPath = oldConfigPath
   254  	}()
   255  	configKey = nil // reset password
   256  	c, err := loadConfigFile()
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  	sections := c.GetSectionList()
   261  	var expect = []string{"RCLONE_ENCRYPT_V0", "nounc", "unc"}
   262  	assert.Equal(t, expect, sections)
   263  
   264  	keys := c.GetKeyList("nounc")
   265  	expect = []string{"type", "nounc"}
   266  	assert.Equal(t, expect, keys)
   267  }
   268  
   269  func TestConfigLoadEncrypted(t *testing.T) {
   270  	var err error
   271  	oldConfigPath := ConfigPath
   272  	ConfigPath = "./testdata/encrypted.conf"
   273  	defer func() {
   274  		ConfigPath = oldConfigPath
   275  		configKey = nil // reset password
   276  	}()
   277  
   278  	// Set correct password
   279  	err = setConfigPassword("asdf")
   280  	require.NoError(t, err)
   281  	c, err := loadConfigFile()
   282  	require.NoError(t, err)
   283  	sections := c.GetSectionList()
   284  	var expect = []string{"nounc", "unc"}
   285  	assert.Equal(t, expect, sections)
   286  
   287  	keys := c.GetKeyList("nounc")
   288  	expect = []string{"type", "nounc"}
   289  	assert.Equal(t, expect, keys)
   290  }
   291  
   292  func TestConfigLoadEncryptedWithValidPassCommand(t *testing.T) {
   293  	oldConfigPath := ConfigPath
   294  	oldConfig := fs.Config
   295  	ConfigPath = "./testdata/encrypted.conf"
   296  	// using fs.Config.PasswordCommand, correct password
   297  	fs.Config.PasswordCommand = fs.SpaceSepList{"echo", "asdf"}
   298  	defer func() {
   299  		ConfigPath = oldConfigPath
   300  		configKey = nil // reset password
   301  		fs.Config = oldConfig
   302  		fs.Config.PasswordCommand = nil
   303  	}()
   304  
   305  	configKey = nil // reset password
   306  
   307  	c, err := loadConfigFile()
   308  	require.NoError(t, err)
   309  
   310  	sections := c.GetSectionList()
   311  	var expect = []string{"nounc", "unc"}
   312  	assert.Equal(t, expect, sections)
   313  
   314  	keys := c.GetKeyList("nounc")
   315  	expect = []string{"type", "nounc"}
   316  	assert.Equal(t, expect, keys)
   317  }
   318  
   319  func TestConfigLoadEncryptedWithInvalidPassCommand(t *testing.T) {
   320  	oldConfigPath := ConfigPath
   321  	oldConfig := fs.Config
   322  	ConfigPath = "./testdata/encrypted.conf"
   323  	// using fs.Config.PasswordCommand, incorrect password
   324  	fs.Config.PasswordCommand = fs.SpaceSepList{"echo", "asdf-blurfl"}
   325  	defer func() {
   326  		ConfigPath = oldConfigPath
   327  		configKey = nil // reset password
   328  		fs.Config = oldConfig
   329  		fs.Config.PasswordCommand = nil
   330  	}()
   331  
   332  	configKey = nil // reset password
   333  
   334  	_, err := loadConfigFile()
   335  	require.Error(t, err)
   336  	assert.Contains(t, err.Error(), "using --password-command derived password")
   337  }
   338  
   339  func TestConfigLoadEncryptedFailures(t *testing.T) {
   340  	var err error
   341  
   342  	// This file should be too short to be decoded.
   343  	oldConfigPath := ConfigPath
   344  	ConfigPath = "./testdata/enc-short.conf"
   345  	defer func() { ConfigPath = oldConfigPath }()
   346  	_, err = loadConfigFile()
   347  	require.Error(t, err)
   348  
   349  	// This file contains invalid base64 characters.
   350  	ConfigPath = "./testdata/enc-invalid.conf"
   351  	_, err = loadConfigFile()
   352  	require.Error(t, err)
   353  
   354  	// This file contains invalid base64 characters.
   355  	ConfigPath = "./testdata/enc-too-new.conf"
   356  	_, err = loadConfigFile()
   357  	require.Error(t, err)
   358  
   359  	// This file does not exist.
   360  	ConfigPath = "./testdata/filenotfound.conf"
   361  	c, err := loadConfigFile()
   362  	assert.Equal(t, errorConfigFileNotFound, err)
   363  	assert.Nil(t, c)
   364  }
   365  
   366  func TestPassword(t *testing.T) {
   367  	defer func() {
   368  		configKey = nil // reset password
   369  	}()
   370  	var err error
   371  	// Empty password should give error
   372  	err = setConfigPassword("  \t  ")
   373  	require.Error(t, err)
   374  
   375  	// Test invalid utf8 sequence
   376  	err = setConfigPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc")
   377  	require.Error(t, err)
   378  
   379  	// Simple check of wrong passwords
   380  	hashedKeyCompare(t, "mis", "match", false)
   381  
   382  	// Check that passwords match after unicode normalization
   383  	hashedKeyCompare(t, "ff\u0041\u030A", "ffÅ", true)
   384  
   385  	// Check that passwords preserves case
   386  	hashedKeyCompare(t, "abcdef", "ABCDEF", false)
   387  
   388  }
   389  
   390  func hashedKeyCompare(t *testing.T, a, b string, shouldMatch bool) {
   391  	err := setConfigPassword(a)
   392  	require.NoError(t, err)
   393  	k1 := configKey
   394  
   395  	err = setConfigPassword(b)
   396  	require.NoError(t, err)
   397  	k2 := configKey
   398  
   399  	if shouldMatch {
   400  		assert.Equal(t, k1, k2)
   401  	} else {
   402  		assert.NotEqual(t, k1, k2)
   403  	}
   404  }
   405  
   406  func TestMatchProvider(t *testing.T) {
   407  	for _, test := range []struct {
   408  		config   string
   409  		provider string
   410  		want     bool
   411  	}{
   412  		{"", "", true},
   413  		{"one", "one", true},
   414  		{"one,two", "two", true},
   415  		{"one,two,three", "two", true},
   416  		{"one", "on", false},
   417  		{"one,two,three", "tw", false},
   418  		{"!one,two,three", "two", false},
   419  		{"!one,two,three", "four", true},
   420  	} {
   421  		what := fmt.Sprintf("%q,%q", test.config, test.provider)
   422  		got := matchProvider(test.config, test.provider)
   423  		assert.Equal(t, test.want, got, what)
   424  	}
   425  }
   426  
   427  func TestFileRefresh(t *testing.T) {
   428  	defer testConfigFile(t, "refresh.conf")()
   429  	require.NoError(t, CreateRemote("refresh_test", "config_test_remote", rc.Params{
   430  		"bool": true,
   431  	}, false, false))
   432  	b, err := ioutil.ReadFile(ConfigPath)
   433  	assert.NoError(t, err)
   434  
   435  	b = bytes.Replace(b, []byte("refresh_test"), []byte("refreshed_test"), 1)
   436  	err = ioutil.WriteFile(ConfigPath, b, 0644)
   437  	assert.NoError(t, err)
   438  
   439  	assert.NotEqual(t, []string{"refreshed_test"}, configFile.GetSectionList())
   440  	err = FileRefresh()
   441  	assert.NoError(t, err)
   442  	assert.Equal(t, []string{"refreshed_test"}, configFile.GetSectionList())
   443  }