mirror of
https://github.com/rclone/rclone.git
synced 2025-01-24 07:10:25 +01:00
seafile: fix 2fa state machine
This commit is contained in:
parent
badefdb060
commit
00c6642fad
@ -325,17 +325,20 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
|
|||||||
|
|
||||||
switch config.State {
|
switch config.State {
|
||||||
case "":
|
case "":
|
||||||
// Just make sure we do have a password
|
// Empty state means it's the first call to the Config function
|
||||||
if password == "" {
|
if password == "" {
|
||||||
return fs.ConfigPassword("", "config_password", "Two-factor authentication: please enter your password (it won't be saved in the configuration)")
|
return fs.ConfigPassword("password", "config_password", "Two-factor authentication: please enter your password (it won't be saved in the configuration)")
|
||||||
}
|
}
|
||||||
return fs.ConfigGoto("password")
|
// password was successfully loaded from the config
|
||||||
|
return fs.ConfigGoto("2fa")
|
||||||
case "password":
|
case "password":
|
||||||
|
// password should be coming from the previous state (entered by the user)
|
||||||
password = config.Result
|
password = config.Result
|
||||||
if password == "" {
|
if password == "" {
|
||||||
return fs.ConfigError("password", "Password can't be blank")
|
return fs.ConfigError("", "Password can't be blank")
|
||||||
}
|
}
|
||||||
m.Set(configPassword, obscure.MustObscure(config.Result))
|
// save it into the configuration file and keep going
|
||||||
|
m.Set(configPassword, obscure.MustObscure(password))
|
||||||
return fs.ConfigGoto("2fa")
|
return fs.ConfigGoto("2fa")
|
||||||
case "2fa":
|
case "2fa":
|
||||||
return fs.ConfigInput("2fa_do", "config_2fa", "Two-factor authentication: please enter your 2FA code")
|
return fs.ConfigInput("2fa_do", "config_2fa", "Two-factor authentication: please enter your 2FA code")
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package seafile
|
package seafile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/config/configmap"
|
||||||
|
"github.com/rclone/rclone/fs/config/obscure"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pathData struct {
|
type pathData struct {
|
||||||
@ -19,77 +24,77 @@ type pathData struct {
|
|||||||
// from a mix of configuration data and path command line argument
|
// from a mix of configuration data and path command line argument
|
||||||
func TestSplitPath(t *testing.T) {
|
func TestSplitPath(t *testing.T) {
|
||||||
testData := []pathData{
|
testData := []pathData{
|
||||||
pathData{
|
{
|
||||||
configLibrary: "",
|
configLibrary: "",
|
||||||
configRoot: "",
|
configRoot: "",
|
||||||
argumentPath: "",
|
argumentPath: "",
|
||||||
expectedLibrary: "",
|
expectedLibrary: "",
|
||||||
expectedPath: "",
|
expectedPath: "",
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "",
|
configLibrary: "",
|
||||||
configRoot: "",
|
configRoot: "",
|
||||||
argumentPath: "Library",
|
argumentPath: "Library",
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: "",
|
expectedPath: "",
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "",
|
configLibrary: "",
|
||||||
configRoot: "",
|
configRoot: "",
|
||||||
argumentPath: path.Join("Library", "path", "to", "file"),
|
argumentPath: path.Join("Library", "path", "to", "file"),
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: path.Join("path", "to", "file"),
|
expectedPath: path.Join("path", "to", "file"),
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: "",
|
configRoot: "",
|
||||||
argumentPath: "",
|
argumentPath: "",
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: "",
|
expectedPath: "",
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: "",
|
configRoot: "",
|
||||||
argumentPath: "path",
|
argumentPath: "path",
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: "path",
|
expectedPath: "path",
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: "",
|
configRoot: "",
|
||||||
argumentPath: path.Join("path", "to", "file"),
|
argumentPath: path.Join("path", "to", "file"),
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: path.Join("path", "to", "file"),
|
expectedPath: path.Join("path", "to", "file"),
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: "root",
|
configRoot: "root",
|
||||||
argumentPath: "",
|
argumentPath: "",
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: "root",
|
expectedPath: "root",
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: path.Join("root", "path"),
|
configRoot: path.Join("root", "path"),
|
||||||
argumentPath: "",
|
argumentPath: "",
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: path.Join("root", "path"),
|
expectedPath: path.Join("root", "path"),
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: "root",
|
configRoot: "root",
|
||||||
argumentPath: "path",
|
argumentPath: "path",
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: path.Join("root", "path"),
|
expectedPath: path.Join("root", "path"),
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: "root",
|
configRoot: "root",
|
||||||
argumentPath: path.Join("path", "to", "file"),
|
argumentPath: path.Join("path", "to", "file"),
|
||||||
expectedLibrary: "Library",
|
expectedLibrary: "Library",
|
||||||
expectedPath: path.Join("root", "path", "to", "file"),
|
expectedPath: path.Join("root", "path", "to", "file"),
|
||||||
},
|
},
|
||||||
pathData{
|
{
|
||||||
configLibrary: "Library",
|
configLibrary: "Library",
|
||||||
configRoot: path.Join("root", "path"),
|
configRoot: path.Join("root", "path"),
|
||||||
argumentPath: path.Join("subpath", "to", "file"),
|
argumentPath: path.Join("subpath", "to", "file"),
|
||||||
@ -121,3 +126,98 @@ func TestSplitPathIntoSlice(t *testing.T) {
|
|||||||
assert.Equal(t, expected, output)
|
assert.Equal(t, expected, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test2FAStateMachine(t *testing.T) {
|
||||||
|
fixtures := []struct {
|
||||||
|
name string
|
||||||
|
mapper configmap.Mapper
|
||||||
|
input fs.ConfigIn
|
||||||
|
expectState string
|
||||||
|
expectErrorMessage string
|
||||||
|
expectResult string
|
||||||
|
expectFail bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no url",
|
||||||
|
mapper: configmap.Simple{},
|
||||||
|
input: fs.ConfigIn{State: ""},
|
||||||
|
expectFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2fa not set",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/"},
|
||||||
|
input: fs.ConfigIn{State: ""},
|
||||||
|
expectFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown state",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "unknown"},
|
||||||
|
expectFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no password in config",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: ""},
|
||||||
|
expectState: "password",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config ready for 2fa token",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username", "pass": obscure.MustObscure("password")},
|
||||||
|
input: fs.ConfigIn{State: ""},
|
||||||
|
expectState: "2fa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password not entered",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "password"},
|
||||||
|
expectState: "",
|
||||||
|
expectErrorMessage: "Password can't be blank",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password entered",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "password", Result: "password"},
|
||||||
|
expectState: "2fa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ask for a 2fa code",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "2fa"},
|
||||||
|
expectState: "2fa_do",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no 2fa code entered",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "2fa_do"},
|
||||||
|
expectState: "2fa", // ask for a code again
|
||||||
|
expectErrorMessage: "2FA codes can't be blank",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2fa error and retry",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "2fa_error", Result: "true"},
|
||||||
|
expectState: "2fa", // ask for a code again
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2fa error and fail",
|
||||||
|
mapper: configmap.Simple{"url": "http://localhost/", "2fa": "true", "user": "username"},
|
||||||
|
input: fs.ConfigIn{State: "2fa_error"},
|
||||||
|
expectFail: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fixture := range fixtures {
|
||||||
|
t.Run(fixture.name, func(t *testing.T) {
|
||||||
|
output, err := Config(context.Background(), "test", fixture.mapper, fixture.input)
|
||||||
|
if fixture.expectFail {
|
||||||
|
require.Error(t, err)
|
||||||
|
t.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, fixture.expectState, output.State)
|
||||||
|
assert.Equal(t, fixture.expectErrorMessage, output.Error)
|
||||||
|
assert.Equal(t, fixture.expectResult, output.Result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user