From d4b2709fb097bc2b8dde9acdedd779757d4252e8 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Thu, 2 Jul 2020 18:39:14 +0100 Subject: [PATCH] pcloud: fix oauth on European region "eapi.pcloud.com" Pcloud appears to have opened up a new region and they are returning the hostname in the oauth callback, thus GET /?code=XXX&locationid=1&hostname=api.pcloud.com&state=XXX HTTP/1.1 GET /?code=XXX&locationid=2&hostname=eapi.pcloud.com&state=XXX HTTP/1.1 This isn't documented yet, however pCloud have confirmed that this is the correct interpretation. Rclone now reads the "hostname" parameter in the oauth callback and stores it in the config file. It uses it for all subequent API calls. --- backend/pcloud/pcloud.go | 47 +++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/backend/pcloud/pcloud.go b/backend/pcloud/pcloud.go index 450f84b70..465b35ea6 100644 --- a/backend/pcloud/pcloud.go +++ b/backend/pcloud/pcloud.go @@ -42,7 +42,7 @@ const ( minSleep = 10 * time.Millisecond maxSleep = 2 * time.Second decayConstant = 2 // bigger for slower decay, exponential - rootURL = "https://api.pcloud.com" + defaultHostname = "api.pcloud.com" ) // Globals @@ -51,8 +51,8 @@ var ( oauthConfig = &oauth2.Config{ Scopes: nil, Endpoint: oauth2.Endpoint{ - AuthURL: "https://my.pcloud.com/oauth2/authorize", - TokenURL: "https://api.pcloud.com/oauth2_token", + AuthURL: "https://my.pcloud.com/oauth2/authorize", + // TokenURL: "https://api.pcloud.com/oauth2_token", set by updateTokenURL }, ClientID: rcloneClientID, ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret), @@ -60,17 +60,45 @@ var ( } ) +// Update the TokenURL with the actual hostname +func updateTokenURL(oauthConfig *oauth2.Config, hostname string) { + oauthConfig.Endpoint.TokenURL = "https://" + hostname + "/oauth2_token" +} + // Register with Fs func init() { + updateTokenURL(oauthConfig, defaultHostname) fs.Register(&fs.RegInfo{ Name: "pcloud", Description: "Pcloud", NewFs: NewFs, Config: func(name string, m configmap.Mapper) { + optc := new(Options) + err := configstruct.Set(m, optc) + if err != nil { + fs.Errorf(nil, "Failed to read config: %v", err) + } + updateTokenURL(oauthConfig, optc.Hostname) + checkAuth := func(oauthConfig *oauth2.Config, auth *oauthutil.AuthResult) error { + if auth == nil || auth.Form == nil { + return errors.New("form not found in response") + } + hostname := auth.Form.Get("hostname") + if hostname == "" { + hostname = defaultHostname + } + // Save the hostname in the config + m.Set("hostname", hostname) + // Update the token URL + updateTokenURL(oauthConfig, hostname) + fs.Debugf(nil, "pcloud: got hostname %q", hostname) + return nil + } opt := oauthutil.Options{ + CheckAuth: checkAuth, StateBlankOK: true, // pCloud seems to drop the state parameter now - see #4210 } - err := oauthutil.Config("pcloud", name, m, oauthConfig, &opt) + err = oauthutil.Config("pcloud", name, m, oauthConfig, &opt) if err != nil { log.Fatalf("Failed to configure token: %v", err) } @@ -96,6 +124,13 @@ func init() { Help: "Fill in for rclone to use a non root folder as its starting point.", Default: "d0", Advanced: true, + }, { + Name: "hostname", + Help: `Hostname to connect to. + +This is normally set when rclone initially does the oauth connection.`, + Default: defaultHostname, + Advanced: true, }}, }) } @@ -104,6 +139,7 @@ func init() { type Options struct { Enc encoder.MultiEncoder `config:"encoding"` RootFolderID string `config:"root_folder_id"` + Hostname string `config:"hostname"` } // Fs represents a remote pcloud @@ -253,12 +289,13 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { if err != nil { return nil, errors.Wrap(err, "failed to configure Pcloud") } + updateTokenURL(oauthConfig, opt.Hostname) f := &Fs{ name: name, root: root, opt: *opt, - srv: rest.NewClient(oAuthClient).SetRoot(rootURL), + srv: rest.NewClient(oAuthClient).SetRoot("https://" + opt.Hostname), pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), } f.features = (&fs.Features{