From 37e630178ef29ecef371c079bd60315c9aede951 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Thu, 11 Feb 2021 16:29:52 +0000 Subject: [PATCH] dropbox: add scopes to oauth request and optionally "members.read" This change adds the scopes rclone wants during the oauth request. Previously rclone left these blank to get a default set. This allows rclone to add the "members.read" scope which is necessary for "impersonate" to work, but only when it is in use as it require authorisation from a Team Admin. See: https://forum.rclone.org/t/dropbox-no-members-read/22223/3 --- backend/dropbox/dropbox.go | 45 +++++++++++++++++++++++++++++++++----- lib/oauthutil/oauthutil.go | 1 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/backend/dropbox/dropbox.go b/backend/dropbox/dropbox.go index 4b29f5792..b61ef5b3f 100755 --- a/backend/dropbox/dropbox.go +++ b/backend/dropbox/dropbox.go @@ -94,7 +94,14 @@ const ( var ( // Description of how to auth for this app dropboxConfig = &oauth2.Config{ - Scopes: []string{}, + Scopes: []string{ + "files.metadata.write", + "files.content.write", + "files.content.read", + "sharing.write", + // "file_requests.write", + // "members.read", // needed for impersonate - but causes app to need to be approved by Dropbox Team Admin during the flow + }, // Endpoint: oauth2.Endpoint{ // AuthURL: "https://www.dropbox.com/1/oauth2/authorize", // TokenURL: "https://api.dropboxapi.com/1/oauth2/token", @@ -115,6 +122,19 @@ var ( errNotSupportedInSharedMode = fserrors.NoRetryError(errors.New("not supported in shared files mode")) ) +// Gets an oauth config with the right scopes +func getOauthConfig(m configmap.Mapper) *oauth2.Config { + // If not impersonating, use standard scopes + if impersonate, _ := m.Get("impersonate"); impersonate == "" { + return dropboxConfig + } + // Make a copy of the config + config := *dropboxConfig + // Make a copy of the scopes with "members.read" appended + config.Scopes = append(config.Scopes, "members.read") + return &config +} + // Register with Fs func init() { DbHashType = hash.RegisterHash("DropboxHash", 64, dbhash.New) @@ -129,7 +149,7 @@ func init() { oauth2.SetAuthURLParam("token_access_type", "offline"), }, } - err := oauthutil.Config(ctx, "dropbox", name, m, dropboxConfig, &opt) + err := oauthutil.Config(ctx, "dropbox", name, m, getOauthConfig(m), &opt) if err != nil { log.Fatalf("Failed to configure token: %v", err) } @@ -147,8 +167,23 @@ memory. It can be set smaller if you are tight on memory.`, maxChunkSize), Default: defaultChunkSize, Advanced: true, }, { - Name: "impersonate", - Help: "Impersonate this user when using a business account.", + Name: "impersonate", + Help: `Impersonate this user when using a business account. + +Note that if you want to use impersonate, you should make sure this +flag is set when running "rclone config" as this will cause rclone to +request the "members.read" scope which it won't normally. This is +needed to lookup a members email address into the internal ID that +dropbox uses in the API. + +Using the "members.read" scope will require a Dropbox Team Admin +to approve during the OAuth flow. + +You will have to use your own App (setting your own client_id and +client_secret) to use this option as currently rclone's default set of +permissions doesn't include "members.read". This can be added once +v1.55 or later is in use everywhere. +`, Default: "", Advanced: true, }, { @@ -327,7 +362,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e } } - oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, dropboxConfig) + oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, getOauthConfig(m)) if err != nil { return nil, errors.Wrap(err, "failed to configure dropbox") } diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go index 501a36d3d..f332d8af3 100644 --- a/lib/oauthutil/oauthutil.go +++ b/lib/oauthutil/oauthutil.go @@ -639,6 +639,7 @@ func (s *authServer) Init() error { http.Error(w, "State did not match - please try again", http.StatusForbidden) return } + fs.Debugf(nil, "Redirecting browser to: %s", s.authURL) http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect) return })