From b5a81dab768ad97b5c447ba0064a1b41617eb70f Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 7 Jul 2026 11:53:45 +0100 Subject: [PATCH] drive, googlephotos: warn in config wizard before using the shared client_id #9580 rclone's shared Google Drive and Google Photos client_id is being retired and will stop working during 2026. When creating a new remote that would use it, the config wizard now warns the user and asks the user to enter their own client_id and secret instead. Service account and environment auth are unaffected as they don't use the shared client_id. See: https://forum.rclone.org/t/google-drive-and-google-photos-users-action-required/54005 --- backend/drive/drive.go | 42 ++++++++++++++++------ backend/googlephotos/googlephotos.go | 44 ++++++++++++++++++++--- docs/content/drive.md | 52 +++++++++++++++++++--------- docs/content/googlephotos.md | 39 +++++++++++++++------ lib/oauthutil/oauthutil.go | 19 ++++++++++ 5 files changed, 156 insertions(+), 40 deletions(-) diff --git a/backend/drive/drive.go b/backend/drive/drive.go index 40a770b7c..b90593f84 100644 --- a/backend/drive/drive.go +++ b/backend/drive/drive.go @@ -208,7 +208,10 @@ func driveOAuthOptions() []fs.Option { opts := []fs.Option{} for _, opt := range oauthutil.SharedOptions { if opt.Name == config.ConfigClientID { - opt.Help = "Google Application Client Id\nSetting your own is recommended.\nSee https://rclone.org/drive/#making-your-own-client-id for how to create your own.\nIf you leave this blank, it will use an internal key which is low performance." + opt.Help = "Google Application Client Id\nLeave blank to use rclone's shared client_id, or if you are using a service account.\nThe shared client_id is being retired and will stop working during 2026, so creating your own is now strongly recommended.\nSee https://rclone.org/drive/#making-your-own-client-id for how to create your own." + } + if opt.Name == config.ConfigClientSecret { + opt.Help = "Google Application Client Secret\nLeave blank to use rclone's shared client_id, or if you are using a service account.\nIf you created your own client_id then enter its client secret here." } opts = append(opts, opt) } @@ -222,7 +225,7 @@ func init() { Description: "Google Drive", NewFs: NewFs, CommandHelp: commandHelp, - Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) { + Config: func(ctx context.Context, name string, m configmap.Mapper, configIn fs.ConfigIn) (*fs.ConfigOut, error) { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) @@ -230,7 +233,7 @@ func init() { return nil, fmt.Errorf("couldn't parse config into struct: %w", err) } - switch config.State { + switch configIn.State { case "": // Fill in the scopes driveConfig.Scopes = driveScopes(opt.Scope) @@ -241,24 +244,43 @@ func init() { } if opt.ServiceAccountFile == "" && opt.ServiceAccountCredentials == "" && !opt.EnvAuth { - return oauthutil.ConfigOut("teamdrive", &oauthutil.Options{ - OAuth2Config: driveConfig, - }) + return fs.ConfigGoto("client_id") } return fs.ConfigGoto("teamdrive") + case "client_id": + if clientID, _ := m.Get(config.ConfigClientID); clientID != "" { + return fs.ConfigGoto("oauth") + } + return oauthutil.SharedClientIDConfigConfirm("client_id_warning", "Google Drive", "https://rclone.org/drive/#making-your-own-client-id") + case "client_id_warning": + if configIn.Result == "true" { + // Continue using the shared client_id + return fs.ConfigGoto("oauth") + } + return fs.ConfigInput("client_id_set", config.ConfigClientID, "Google Application Client Id") + case "client_id_set": + m.Set(config.ConfigClientID, configIn.Result) + return fs.ConfigInput("client_secret_set", config.ConfigClientSecret, "Google Application Client Secret") + case "client_secret_set": + m.Set(config.ConfigClientSecret, configIn.Result) + return fs.ConfigGoto("oauth") + case "oauth": + return oauthutil.ConfigOut("teamdrive", &oauthutil.Options{ + OAuth2Config: driveConfig, + }) case "teamdrive": if opt.TeamDriveID == "" { return fs.ConfigConfirm("teamdrive_ok", false, "config_change_team_drive", "Configure this as a Shared Drive (Team Drive)?\n") } return fs.ConfigConfirm("teamdrive_change", false, "config_change_team_drive", fmt.Sprintf("Change current Shared Drive (Team Drive) ID %q?\n", opt.TeamDriveID)) case "teamdrive_ok": - if config.Result == "false" { + if configIn.Result == "false" { m.Set("team_drive", "") return nil, nil } return fs.ConfigGoto("teamdrive_config") case "teamdrive_change": - if config.Result == "false" { + if configIn.Result == "false" { return nil, nil } return fs.ConfigGoto("teamdrive_config") @@ -279,14 +301,14 @@ func init() { return teamDrive.Id, teamDrive.Name }) case "teamdrive_final": - driveID := config.Result + driveID := configIn.Result m.Set("team_drive", driveID) m.Set("root_folder_id", "") opt.TeamDriveID = driveID opt.RootFolderID = "" return nil, nil } - return nil, fmt.Errorf("unknown state %q", config.State) + return nil, fmt.Errorf("unknown state %q", configIn.State) }, MetadataInfo: &fs.MetadataInfo{ System: systemMetadataInfo, diff --git a/backend/googlephotos/googlephotos.go b/backend/googlephotos/googlephotos.go index 6ecb4ce38..39e9f9e5d 100644 --- a/backend/googlephotos/googlephotos.go +++ b/backend/googlephotos/googlephotos.go @@ -94,6 +94,23 @@ var ( } ) +// gphotosOAuthOptions returns a copy of oauthutil.SharedOptions with +// the client_id help tailored for Google Photos. It copies the shared +// options rather than mutating the global slice. +func gphotosOAuthOptions() []fs.Option { + opts := []fs.Option{} + for _, opt := range oauthutil.SharedOptions { + if opt.Name == config.ConfigClientID { + opt.Help = "OAuth Client Id.\n\nCreating your own is now strongly recommended.\nIf you leave this blank rclone uses a shared client_id which is being retired and will stop working during 2026.\nSee https://rclone.org/googlephotos/#making-your-own-client-id for how to create your own." + } + if opt.Name == config.ConfigClientSecret { + opt.Help = "OAuth Client Secret.\n\nLeave blank to use rclone's shared client_id.\nIf you created your own client_id then enter its client secret here." + } + opts = append(opts, opt) + } + return opts +} + // Register with Fs func init() { fs.Register(&fs.RegInfo{ @@ -101,7 +118,7 @@ func init() { Prefix: "gphotos", Description: "Google Photos", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) { + Config: func(ctx context.Context, name string, m configmap.Mapper, configIn fs.ConfigIn) (*fs.ConfigOut, error) { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) @@ -109,7 +126,7 @@ func init() { return nil, fmt.Errorf("couldn't parse config into struct: %w", err) } - switch config.State { + switch configIn.State { case "": // Fill in the scopes if opt.ReadOnly { @@ -117,6 +134,25 @@ func init() { } else { oauthConfig.Scopes = scopesReadWrite } + return fs.ConfigGoto("client_id") + case "client_id": + if clientID, _ := m.Get(config.ConfigClientID); clientID != "" { + return fs.ConfigGoto("oauth") + } + return oauthutil.SharedClientIDConfigConfirm("client_id_warning", "Google Photos", "https://rclone.org/googlephotos/#making-your-own-client-id") + case "client_id_warning": + if configIn.Result == "true" { + // Continue using the shared client_id + return fs.ConfigGoto("oauth") + } + return fs.ConfigInput("client_id_set", config.ConfigClientID, "OAuth Client Id") + case "client_id_set": + m.Set(config.ConfigClientID, configIn.Result) + return fs.ConfigInput("client_secret_set", config.ConfigClientSecret, "OAuth Client Secret") + case "client_secret_set": + m.Set(config.ConfigClientSecret, configIn.Result) + return fs.ConfigGoto("oauth") + case "oauth": return oauthutil.ConfigOut("warning1", &oauthutil.Options{ OAuth2Config: oauthConfig, }) @@ -136,9 +172,9 @@ IMPORTANT: Due to Google policy changes rclone can now only download photos it u case "warning_done": return nil, nil } - return nil, fmt.Errorf("unknown state %q", config.State) + return nil, fmt.Errorf("unknown state %q", configIn.State) }, - Options: append(append(oauthutil.SharedOptions, []fs.Option{{ + Options: append(append(gphotosOAuthOptions(), []fs.Option{{ Name: "read_only", Default: false, Help: `Set to make the Google Photos backend read only. diff --git a/docs/content/drive.md b/docs/content/drive.md index 4cb758b6d..1faec7a79 100644 --- a/docs/content/drive.md +++ b/docs/content/drive.md @@ -40,9 +40,14 @@ XX / Google Drive \ "drive" [snip] Storage> drive -Google Application Client Id - leave blank normally. +Google Application Client Id +Leave blank to use rclone's shared client_id, or if you are using a service account. +The shared client_id is being retired and will stop working during 2026, so creating your own is now strongly recommended. +See https://rclone.org/drive/#making-your-own-client-id for how to create your own. client_id> -Google Application Client Secret - leave blank normally. +Google Application Client Secret +Leave blank to use rclone's shared client_id, or if you are using a service account. +If you created your own client_id then enter its client secret here. client_secret> Scope that rclone should use when requesting access from drive. Choose a number from below, or type in your own value @@ -64,6 +69,17 @@ scope> 1 Service Account Credentials JSON file path - needed only if you want use SA instead of interactive login. service_account_file> Remote config +rclone's shared Google Drive client_id is being retired and will stop working during 2026. +Create your own to avoid interruption: https://rclone.org/drive/#making-your-own-client-id + +Continue using the shared client_id anyway? +y) Yes +n) No (default) +y/n> n +Google Application Client Id +client_id> 1234567890-abcdefghijklmnop.apps.googleusercontent.com +Google Application Client Secret +client_secret> GOCSPX-your-client-secret Use web browser to automatically authenticate rclone with remote? * Say Y if the machine running rclone has a web browser you can use * Say N if running rclone on a (remote) machine without web browser access @@ -82,8 +98,8 @@ y/n> n Configuration complete. Options: type: drive -- client_id: -- client_secret: +- client_id: 1234567890-abcdefghijklmnop.apps.googleusercontent.com +- client_secret: GOCSPX-your-client-secret - scope: drive - root_folder_id: - service_account_file: @@ -615,9 +631,9 @@ Here are the Standard options specific to drive (Google Drive). #### --drive-client-id Google Application Client Id -Setting your own is recommended. +Leave blank to use rclone's shared client_id, or if you are using a service account. +The shared client_id is being retired and will stop working during 2026, so creating your own is now strongly recommended. See https://rclone.org/drive/#making-your-own-client-id for how to create your own. -If you leave this blank, it will use an internal key which is low performance. Properties: @@ -628,9 +644,9 @@ Properties: #### --drive-client-secret -OAuth Client Secret. - -Leave blank normally. +Google Application Client Secret +Leave blank to use rclone's shared client_id, or if you are using a service account. +If you created your own client_id then enter its client secret here. Properties: @@ -1945,14 +1961,18 @@ not have SHA1 or SHA256 hashes especially if they were uploaded before 2018. When you use rclone with Google drive in its default configuration you are using rclone's client_id. This is shared between all the rclone -users. There is a global rate limit on the number of queries per -second that each client_id can do set by Google. rclone already has a -high quota and I will continue to make sure it is high enough by -contacting Google. +users. -It is strongly recommended to use your own client ID as the default -rclone ID is heavily used. If you have multiple services running, it -is recommended to use an API key for each service. The default Google +**This shared client_id is being retired and will stop working during +2026.** To avoid interruption you must create and use your own +client_id, so creating one is now required rather than merely +recommended. New remotes created with `rclone config` will warn you if +you leave the client_id blank. + +Using your own client_id has other benefits too. There is a global +rate limit on the number of queries per second that each client_id can +do set by Google. If you have multiple services running, it is +recommended to use an API key for each service. The default Google quota is 10 transactions per second so it is recommended to stay under that number as if you use more than that, it will cause rclone to rate limit and make things slower. diff --git a/docs/content/googlephotos.md b/docs/content/googlephotos.md index e67586bcd..32fb41a16 100644 --- a/docs/content/googlephotos.md +++ b/docs/content/googlephotos.md @@ -50,12 +50,15 @@ XX / Google Photos Storage> google photos ** See help for google photos backend at: https://rclone.org/googlephotos/ ** -Google Application Client Id -Leave blank normally. +OAuth Client Id. +Creating your own is now strongly recommended. +If you leave this blank rclone uses a shared client_id which is being retired and will stop working during 2026. +See https://rclone.org/googlephotos/#making-your-own-client-id for how to create your own. Enter a string value. Press Enter for the default (""). client_id> -Google Application Client Secret -Leave blank normally. +OAuth Client Secret. +Leave blank to use rclone's shared client_id. +If you created your own client_id then enter its client secret here. Enter a string value. Press Enter for the default (""). client_secret> Set to make the Google Photos backend read only. @@ -69,6 +72,17 @@ y) Yes n) No y/n> n Remote config +rclone's shared Google Photos client_id is being retired and will stop working during 2026. +Create your own to avoid interruption: https://rclone.org/googlephotos/#making-your-own-client-id + +Continue using the shared client_id anyway? +y) Yes +n) No (default) +y/n> n +OAuth Client Id. +client_id> 1234567890-abcdefghijklmnop.apps.googleusercontent.com +OAuth Client Secret. +client_secret> GOCSPX-your-client-secret Use web browser to automatically authenticate rclone with remote? * Say Y if the machine running rclone has a web browser you can use * Say N if running rclone on a (remote) machine without web browser access @@ -253,7 +267,9 @@ Here are the Standard options specific to google photos (Google Photos). OAuth Client Id. -Leave blank normally. +Creating your own is now strongly recommended. +If you leave this blank rclone uses a shared client_id which is being retired and will stop working during 2026. +See https://rclone.org/googlephotos/#making-your-own-client-id for how to create your own. Properties: @@ -266,7 +282,8 @@ Properties: OAuth Client Secret. -Leave blank normally. +Leave blank to use rclone's shared client_id. +If you created your own client_id then enter its client secret here. Properties: @@ -653,11 +670,13 @@ The Google Photos API does not support deleting albums - see [bug #135714733](ht When you use rclone with Google photos in its default configuration you are using rclone's client_id. This is shared between all the rclone -users. There is a global rate limit on the number of queries per -second that each client_id can do set by Google. +users. -If there is a problem with this client_id (eg quota too low or the -client_id stops working) then you can make your own. +**This shared client_id is being retired and will stop working during +2026.** To avoid interruption you must create and use your own +client_id, so creating one is now required rather than optional. New +remotes created with `rclone config` will warn you if you leave the +client_id blank. Please follow the steps in [the google drive docs](https://rclone.org/drive/#making-your-own-client-id) with the following differences: diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go index fb713ba88..99ac0228f 100644 --- a/lib/oauthutil/oauthutil.go +++ b/lib/oauthutil/oauthutil.go @@ -502,6 +502,25 @@ func SharedClientIDWarning(name, service, helpURL string, m configmap.Mapper) { fs.Logf(name, "This remote uses rclone's shared %s client_id, which is being retired and will stop working during 2026. Create your own client_id to avoid interruption: %s", service, helpURL) } +// SharedClientIDConfigConfirm returns a config wizard Yes/No step +// warning that this remote would use rclone's shared client_id (which +// is being retired) and asking whether to continue with it anyway. It +// should only be used when the user has left client_id blank on the +// OAuth path. +// +// state is the config state to go to next, service is the human +// readable name of the service (eg "Google Drive") and helpURL points +// at the docs describing how to make your own client_id. +// +// The question defaults to No to steer the user towards making their +// own client_id. +func SharedClientIDConfigConfirm(state, service, helpURL string) (*fs.ConfigOut, error) { + return fs.ConfigConfirm(state, false, "config_shared_client_id", fmt.Sprintf(`rclone's shared %s client_id is being retired and will stop working during 2026. +Create your own to avoid interruption: %s + +Continue using the shared client_id anyway?`, service, helpURL)) +} + // NewClientWithBaseClient gets a token from the config file and // configures a Client with it. It returns the client and a // TokenSource which Invalidate may need to be called on. It uses the