From bcbd30bb8a7cb146d39c56ce7bac5e77b3ff5fa7 Mon Sep 17 00:00:00 2001 From: klauspost Date: Mon, 4 Jan 2016 16:13:36 +0100 Subject: [PATCH] Add easier headless configuration. This will allow setting up a remote with copy&paste of values to a headless machine. It will allow copy+pasting a token into the configuration. This requires rclone to be on a machine with a proper browser. Custom client id and secrets are supported. To test token generation, use `rclone auth "fs type"`. --- amazonclouddrive/amazonclouddrive.go | 2 +- drive/drive.go | 2 +- fs/config.go | 44 ++++++++++++++++ googlecloudstorage/googlecloudstorage.go | 2 +- hubic/hubic.go | 2 +- oauthutil/oauthutil.go | 65 +++++++++++++++++++++--- onedrive/onedrive.go | 2 +- rclone.go | 12 +++++ yandex/yandex.go | 2 +- 9 files changed, 120 insertions(+), 13 deletions(-) diff --git a/amazonclouddrive/amazonclouddrive.go b/amazonclouddrive/amazonclouddrive.go index 97328435e..9bfda9b0a 100644 --- a/amazonclouddrive/amazonclouddrive.go +++ b/amazonclouddrive/amazonclouddrive.go @@ -63,7 +63,7 @@ func init() { Name: "amazon cloud drive", NewFs: NewFs, Config: func(name string) { - err := oauthutil.Config(name, acdConfig) + err := oauthutil.Config("amazon cloud drive", name, acdConfig) if err != nil { log.Fatalf("Failed to configure token: %v", err) } diff --git a/drive/drive.go b/drive/drive.go index 8c1d1fa43..7cbc58555 100644 --- a/drive/drive.go +++ b/drive/drive.go @@ -66,7 +66,7 @@ func init() { Name: "drive", NewFs: NewFs, Config: func(name string) { - err := oauthutil.Config(name, driveConfig) + err := oauthutil.Config("drive", name, driveConfig) if err != nil { log.Fatalf("Failed to configure token: %v", err) } diff --git a/fs/config.go b/fs/config.go index 171f7e64b..b1714872a 100644 --- a/fs/config.go +++ b/fs/config.go @@ -532,3 +532,47 @@ func EditConfig() { } } } + +// Duplicated from oauthutil to avoid circular reference. +const ( + // ConfigClientID is the config key used to store the client id + ConfigClientID = "client_id" + + // ConfigClientSecret is the config key used to store the client secret + ConfigClientSecret = "client_secret" + + // ConfigAutomatic indicates that we want non-interactive configuration + ConfigAutomatic = "config_automatic" +) + +// Authorize is for remote authorization of headless machines. +func Authorize() { + args := pflag.Args()[1:] + switch len(args) { + case 1, 3: + default: + log.Fatalf("Invalid number of arguments: %d", len(args)) + } + newType := args[0] + fs, err := Find(newType) + if err != nil { + log.Fatalf("Failed to find fs: %v", err) + } + + if fs.Config == nil { + log.Fatalf("No configuration on fs %v", newType) + } + // Name used for temporary fs + name := "**temp-fs**" + + // Make sure we delete it + defer DeleteRemote(name) + + // Indicate that we want fully automatic configuration. + ConfigFile.SetValue(name, ConfigAutomatic, "yes") + if len(args) == 3 { + ConfigFile.SetValue(name, ConfigClientID, args[1]) + ConfigFile.SetValue(name, ConfigClientSecret, args[2]) + } + fs.Config(name) +} diff --git a/googlecloudstorage/googlecloudstorage.go b/googlecloudstorage/googlecloudstorage.go index f16043fa9..ceb139a5a 100644 --- a/googlecloudstorage/googlecloudstorage.go +++ b/googlecloudstorage/googlecloudstorage.go @@ -59,7 +59,7 @@ func init() { Name: "google cloud storage", NewFs: NewFs, Config: func(name string) { - err := oauthutil.Config(name, storageConfig) + err := oauthutil.Config("google cloud storage", name, storageConfig) if err != nil { log.Fatalf("Failed to configure token: %v", err) } diff --git a/hubic/hubic.go b/hubic/hubic.go index 5f522c72f..7f3d716a8 100644 --- a/hubic/hubic.go +++ b/hubic/hubic.go @@ -48,7 +48,7 @@ func init() { Name: "hubic", NewFs: NewFs, Config: func(name string) { - err := oauthutil.Config(name, oauthConfig) + err := oauthutil.Config("hubic", name, oauthConfig) if err != nil { log.Fatalf("Failed to configure token: %v", err) } diff --git a/oauthutil/oauthutil.go b/oauthutil/oauthutil.go index 6684a5547..1d1d212cf 100644 --- a/oauthutil/oauthutil.go +++ b/oauthutil/oauthutil.go @@ -7,6 +7,7 @@ import ( "log" "net" "net/http" + "strings" "time" "github.com/ncw/rclone/fs" @@ -25,6 +26,9 @@ const ( // ConfigClientSecret is the config key used to store the client secret ConfigClientSecret = "client_secret" + // ConfigAutomatic indicates that we want non-interactive configuration + ConfigAutomatic = "config_automatic" + // TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization // code should be returned in the title bar of the browser, with the page text // prompting the user to copy the code and paste it in the application. @@ -147,16 +151,21 @@ func Context() context.Context { } // overrideCredentials sets the ClientID and ClientSecret from the -// config file if they are not blank -func overrideCredentials(name string, config *oauth2.Config) { +// config file if they are not blank. +// If any value is overridden, true is returned. +func overrideCredentials(name string, config *oauth2.Config) bool { + changed := false ClientID := fs.ConfigFile.MustValue(name, ConfigClientID) if ClientID != "" { config.ClientID = ClientID + changed = true } ClientSecret := fs.ConfigFile.MustValue(name, ConfigClientSecret) if ClientSecret != "" { config.ClientSecret = ClientSecret + changed = true } + return changed } // NewClient gets a token from the config file and configures a Client @@ -185,8 +194,10 @@ func NewClient(name string, config *oauth2.Config) (*http.Client, error) { // Config does the initial creation of the token // // It may run an internal webserver to receive the results -func Config(name string, config *oauth2.Config) error { - overrideCredentials(name, config) +func Config(id, name string, config *oauth2.Config) error { + changed := overrideCredentials(name, config) + automatic := fs.ConfigFile.MustValue(name, ConfigAutomatic) != "" + // See if already have a token tokenString := fs.ConfigFile.MustValue(name, "token") if tokenString != "" { @@ -201,11 +212,42 @@ func Config(name string, config *oauth2.Config) error { switch config.RedirectURL { case RedirectURL, RedirectPublicURL, RedirectLocalhostURL: useWebServer = true - case TitleBarRedirectURL: + if automatic { + break + } fmt.Printf("Use auto config?\n") fmt.Printf(" * Say Y if not sure\n") - fmt.Printf(" * Say N if you are working on a remote or headless machine or Y didn't work\n") - useWebServer = fs.Confirm() + fmt.Printf(" * Say N if you are working on a remote or headless machine\n") + auto := fs.Confirm() + if !auto { + fmt.Printf("For this to work, you will need rclone available on a machine that has a web browser available.\n") + fmt.Printf("Execute the following on your machine:\n") + if changed { + fmt.Printf("\trclone authorize %q %q %q\n", id, config.ClientID, config.ClientSecret) + } else { + fmt.Printf("\trclone authorize %q\n", id) + } + fmt.Println("Then paste the result below:") + code := "" + for code == "" { + fmt.Printf("result> ") + code = strings.TrimSpace(fs.ReadLine()) + } + token := &oauth2.Token{} + err := json.Unmarshal([]byte(code), token) + if err != nil { + return err + } + return putToken(name, token) + } + case TitleBarRedirectURL: + useWebServer = automatic + if !automatic { + fmt.Printf("Use auto config?\n") + fmt.Printf(" * Say Y if not sure\n") + fmt.Printf(" * Say N if you are working on a remote or headless machine or Y didn't work\n") + useWebServer = fs.Confirm() + } if useWebServer { // copy the config and set to use the internal webserver configCopy := *config @@ -260,6 +302,15 @@ func Config(name string, config *oauth2.Config) error { if err != nil { return fmt.Errorf("Failed to get token: %v", err) } + + // Print code if we do automatic retrieval + if automatic { + result, err := json.Marshal(token) + if err != nil { + return fmt.Errorf("Failed to marshal token: %v", err) + } + fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste", result) + } return putToken(name, token) } diff --git a/onedrive/onedrive.go b/onedrive/onedrive.go index ceaa5025f..615b4d1ee 100644 --- a/onedrive/onedrive.go +++ b/onedrive/onedrive.go @@ -59,7 +59,7 @@ func init() { Name: "onedrive", NewFs: NewFs, Config: func(name string) { - err := oauthutil.Config(name, oauthConfig) + err := oauthutil.Config("onedrive", name, oauthConfig) if err != nil { log.Fatalf("Failed to configure token: %v", err) } diff --git a/rclone.go b/rclone.go index 17f9f1362..3120c67d9 100644 --- a/rclone.go +++ b/rclone.go @@ -224,6 +224,18 @@ var Commands = []Command{ }, NoStats: true, }, + { + Name: "authorize", + Help: ` + Remote authorization.`, + Run: func(fdst, fsrc fs.Fs) error { + fs.Authorize() + return nil + }, + NoStats: true, + MinArgs: 1, + MaxArgs: 3, + }, { Name: "help", Help: ` diff --git a/yandex/yandex.go b/yandex/yandex.go index 68f1b15cf..6f55397be 100644 --- a/yandex/yandex.go +++ b/yandex/yandex.go @@ -45,7 +45,7 @@ func init() { Name: "yandex", NewFs: NewFs, Config: func(name string) { - err := oauthutil.Config(name, oauthConfig) + err := oauthutil.Config("yandex", name, oauthConfig) if err != nil { log.Fatalf("Failed to configure token: %v", err) }