2022-08-28 11:21:57 +00:00
// Package oauthutil provides OAuth utilities.
2015-08-18 07:55:09 +00:00
package oauthutil
import (
2018-04-06 18:13:27 +00:00
"context"
2015-08-18 07:55:09 +00:00
"encoding/json"
2021-11-04 10:12:57 +00:00
"errors"
2015-08-18 07:55:09 +00:00
"fmt"
2018-06-03 21:49:11 +00:00
"html/template"
2015-09-02 22:37:42 +00:00
"net"
2015-08-18 07:55:09 +00:00
"net/http"
2019-08-30 10:52:03 +00:00
"net/url"
2023-02-24 15:08:38 +00:00
"os"
2021-04-29 08:28:18 +00:00
"strings"
2016-05-23 17:03:22 +00:00
"sync"
2015-08-18 07:55:09 +00:00
"time"
2019-07-28 17:47:38 +00:00
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap"
2021-04-18 04:04:13 +00:00
"github.com/rclone/rclone/fs/fserrors"
2019-07-28 17:47:38 +00:00
"github.com/rclone/rclone/fs/fshttp"
2019-08-30 10:52:03 +00:00
"github.com/rclone/rclone/lib/random"
2015-09-02 22:37:42 +00:00
"github.com/skratchdot/open-golang/open"
2015-08-18 07:55:09 +00:00
"golang.org/x/oauth2"
)
2023-02-24 15:08:38 +00:00
var (
// templateString is the template used in the authorization webserver
templateString string
)
2015-09-12 13:17:39 +00:00
const (
// 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.
TitleBarRedirectURL = "urn:ietf:wg:oauth:2.0:oob"
2015-10-04 21:06:53 +00:00
// bindPort is the port that we bind the local webserver to
bindPort = "53682"
// bindAddress is binding for local webserver when active
bindAddress = "127.0.0.1:" + bindPort
2015-09-12 13:17:39 +00:00
// RedirectURL is redirect to local webserver when active
RedirectURL = "http://" + bindAddress + "/"
2015-10-04 21:06:53 +00:00
// RedirectPublicURL is redirect to local webserver when active with public name
RedirectPublicURL = "http://localhost.rclone.org:" + bindPort + "/"
2015-11-08 15:29:19 +00:00
// RedirectLocalhostURL is redirect to local webserver when active with localhost
RedirectLocalhostURL = "http://localhost:" + bindPort + "/"
2018-06-03 21:49:11 +00:00
2019-08-27 21:48:43 +00:00
// RedirectPublicSecureURL is a public https URL which
// redirects to the local webserver
RedirectPublicSecureURL = "https://oauth.rclone.org/"
2023-02-24 15:08:38 +00:00
// DefaultAuthResponseTemplate is the default template used in the authorization webserver
DefaultAuthResponseTemplate = ` < ! DOCTYPE html >
2018-06-03 21:49:11 +00:00
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > { { if . OK } } Success ! { { else } } Failure ! { { end } } < / title >
< / head >
< body >
< h1 > { { if . OK } } Success ! { { else } } Failure ! { { end } } < / h1 >
< hr >
< pre style = "width: 750px; white-space: pre-wrap;" >
{ { if eq . OK false } }
2019-08-30 10:52:03 +00:00
Error : { { . Name } } < br >
{ { if . Description } } Description : { { . Description } } < br > { { end } }
{ { if . Code } } Code : { { . Code } } < br > { { end } }
{ { if . HelpURL } } Look here for help : < a href = "{{ .HelpURL }}" > { { . HelpURL } } < / a > < br > { { end } }
2018-06-03 21:49:11 +00:00
{ { else } }
All done . Please go back to rclone .
{ { end } }
< / pre >
< / body >
< / html >
`
2015-09-12 13:17:39 +00:00
)
2015-08-18 07:55:09 +00:00
2020-08-01 23:32:21 +00:00
// SharedOptions are shared between backends the utilize an OAuth flow
var SharedOptions = [ ] fs . Option { {
Name : config . ConfigClientID ,
2021-08-16 09:30:01 +00:00
Help : "OAuth Client Id.\n\nLeave blank normally." ,
2020-08-01 23:32:21 +00:00
} , {
Name : config . ConfigClientSecret ,
2021-08-16 09:30:01 +00:00
Help : "OAuth Client Secret.\n\nLeave blank normally." ,
2020-08-01 23:32:21 +00:00
} , {
Name : config . ConfigToken ,
Help : "OAuth Access Token as a JSON blob." ,
Advanced : true ,
} , {
Name : config . ConfigAuthURL ,
2021-08-16 09:30:01 +00:00
Help : "Auth server URL.\n\nLeave blank to use the provider defaults." ,
2020-08-01 23:32:21 +00:00
Advanced : true ,
} , {
Name : config . ConfigTokenURL ,
2021-08-16 09:30:01 +00:00
Help : "Token server url.\n\nLeave blank to use the provider defaults." ,
2020-08-01 23:32:21 +00:00
Advanced : true ,
} }
2015-08-18 07:55:09 +00:00
// oldToken contains an end-user's tokens.
// This is the data you must store to persist authentication.
//
// From the original code.google.com/p/goauth2/oauth package - used
// for backwards compatibility in the rclone config file
type oldToken struct {
AccessToken string
RefreshToken string
Expiry time . Time
}
2017-09-06 07:24:20 +00:00
// GetToken returns the token saved in the config file under
2015-08-18 07:55:09 +00:00
// section name.
2018-05-14 17:06:57 +00:00
func GetToken ( name string , m configmap . Mapper ) ( * oauth2 . Token , error ) {
tokenString , ok := m . Get ( config . ConfigToken )
if ! ok || tokenString == "" {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "empty token found - please run \"rclone config reconnect %s:\"" , name )
2015-08-18 07:55:09 +00:00
}
token := new ( oauth2 . Token )
2016-12-20 18:03:09 +00:00
err := json . Unmarshal ( [ ] byte ( tokenString ) , token )
2015-08-18 07:55:09 +00:00
if err != nil {
return nil , err
}
// if has data then return it
2017-05-21 16:00:01 +00:00
if token . AccessToken != "" {
2015-08-18 07:55:09 +00:00
return token , nil
}
// otherwise try parsing as oldToken
oldtoken := new ( oldToken )
err = json . Unmarshal ( [ ] byte ( tokenString ) , oldtoken )
if err != nil {
return nil , err
}
// Fill in result into new token
token . AccessToken = oldtoken . AccessToken
token . RefreshToken = oldtoken . RefreshToken
token . Expiry = oldtoken . Expiry
// Save new format in config file
2018-05-14 17:06:57 +00:00
err = PutToken ( name , m , token , false )
2015-08-18 07:55:09 +00:00
if err != nil {
return nil , err
}
return token , nil
}
2017-09-06 07:24:20 +00:00
// PutToken stores the token in the config file
2015-08-18 07:55:09 +00:00
//
// This saves the config file if it changes
2018-05-14 17:06:57 +00:00
func PutToken ( name string , m configmap . Mapper , token * oauth2 . Token , newSection bool ) error {
2015-08-18 07:55:09 +00:00
tokenBytes , err := json . Marshal ( token )
if err != nil {
return err
}
tokenString := string ( tokenBytes )
2018-05-14 17:06:57 +00:00
old , ok := m . Get ( config . ConfigToken )
if ! ok || tokenString != old {
2021-03-10 14:13:01 +00:00
m . Set ( config . ConfigToken , tokenString )
fs . Debugf ( name , "Saved new token in config file" )
2015-08-18 07:55:09 +00:00
}
return nil
}
2016-05-23 17:03:22 +00:00
// TokenSource stores updated tokens in the config file
type TokenSource struct {
mu sync . Mutex
name string
2018-05-14 17:06:57 +00:00
m configmap . Mapper
2016-05-23 17:03:22 +00:00
tokenSource oauth2 . TokenSource
token * oauth2 . Token
config * oauth2 . Config
ctx context . Context
2016-08-08 18:02:27 +00:00
expiryTimer * time . Timer // signals whenever the token expires
2015-08-18 07:55:09 +00:00
}
2021-04-18 04:04:13 +00:00
// If token has expired then first try re-reading it (and its refresh token)
// from the config file in case a concurrently running rclone has updated them
// already.
// Returns whether either of the two tokens has been reread.
func ( ts * TokenSource ) reReadToken ( ) ( changed bool ) {
2021-03-10 14:13:01 +00:00
tokenString , found := ts . m . Get ( config . ConfigToken )
if ! found || tokenString == "" {
fs . Debugf ( ts . name , "Failed to read token out of config file" )
2019-01-04 18:06:46 +00:00
return false
}
newToken := new ( oauth2 . Token )
2021-03-10 14:13:01 +00:00
err := json . Unmarshal ( [ ] byte ( tokenString ) , newToken )
2019-01-04 18:06:46 +00:00
if err != nil {
fs . Debugf ( ts . name , "Failed to parse token out of config file: %v" , err )
return false
}
2021-04-18 04:04:13 +00:00
2019-01-04 18:06:46 +00:00
if ! newToken . Valid ( ) {
fs . Debugf ( ts . name , "Loaded invalid token from config file - ignoring" )
2021-04-18 04:04:13 +00:00
} else {
fs . Debugf ( ts . name , "Loaded fresh token from config file" )
changed = true
}
if newToken . RefreshToken != "" && newToken . RefreshToken != ts . token . RefreshToken {
fs . Debugf ( ts . name , "Loaded new refresh token from config file" )
changed = true
}
if changed {
ts . token = newToken
ts . tokenSource = nil // invalidate since we changed the token
2019-01-04 18:06:46 +00:00
}
2021-04-18 04:04:13 +00:00
return changed
2019-01-04 18:06:46 +00:00
}
2023-01-11 16:50:14 +00:00
type retrieveErrResponse struct {
Error string ` json:"error" `
}
// If err is nil or an error other than fatal OAuth errors, returns err itself.
// Otherwise returns a more user-friendly error.
func maybeWrapOAuthError ( err error , remoteName string ) ( newErr error ) {
newErr = err
if rErr , ok := err . ( * oauth2 . RetrieveError ) ; ok {
if rErr . Response . StatusCode == 400 || rErr . Response . StatusCode == 401 {
fs . Debugf ( remoteName , "got fatal oauth error: %v" , rErr )
var resp retrieveErrResponse
if err = json . Unmarshal ( rErr . Body , & resp ) ; err != nil {
newErr = fmt . Errorf ( "(can't decode error info) - try refreshing token with \"rclone config reconnect %s:\"" , remoteName )
return
}
var suggestion string
switch resp . Error {
case "invalid_client" , "unauthorized_client" , "unsupported_grant_type" , "invalid_scope" :
suggestion = fmt . Sprintf ( "if you're using your own client id/secret, make sure they're properly set up following the docs" )
case "invalid_grant" :
fallthrough
default :
suggestion = fmt . Sprintf ( "maybe token expired? - try refreshing with \"rclone config reconnect %s:\"" , remoteName )
}
newErr = fmt . Errorf ( "%s: %s" , resp . Error , suggestion )
}
}
return
}
2015-08-18 07:55:09 +00:00
// Token returns a token or an error.
// Token must be safe for concurrent use by multiple goroutines.
// The returned Token must not be modified.
//
// This saves the token in the config file if it has changed
2016-05-23 17:03:22 +00:00
func ( ts * TokenSource ) Token ( ) ( * oauth2 . Token , error ) {
ts . mu . Lock ( )
defer ts . mu . Unlock ( )
2019-01-04 18:06:46 +00:00
var (
token * oauth2 . Token
err error
changed = false
)
const maxTries = 5
// Try getting the token a few times
for i := 1 ; i <= maxTries ; i ++ {
// Try reading the token from the config file in case it has
// been updated by a concurrent rclone process
if ! ts . token . Valid ( ) {
if ts . reReadToken ( ) {
changed = true
2021-04-18 04:04:13 +00:00
} else if ts . token . RefreshToken == "" {
return nil , fserrors . FatalError (
fmt . Errorf ( "token expired and there's no refresh token - manually refresh with \"rclone config reconnect %s:\"" , ts . name ) ,
)
2019-01-04 18:06:46 +00:00
}
}
2016-05-23 17:03:22 +00:00
2019-01-04 18:06:46 +00:00
// Make a new token source if required
if ts . tokenSource == nil {
ts . tokenSource = ts . config . TokenSource ( ts . ctx , ts . token )
}
2016-05-23 17:03:22 +00:00
2019-01-04 18:06:46 +00:00
token , err = ts . tokenSource . Token ( )
if err == nil {
break
}
2023-01-11 16:50:14 +00:00
if newErr := maybeWrapOAuthError ( err , ts . name ) ; newErr != err {
err = newErr // Fatal OAuth error
break
}
2019-01-04 18:06:46 +00:00
fs . Debugf ( ts . name , "Token refresh failed try %d/%d: %v" , i , maxTries , err )
time . Sleep ( 1 * time . Second )
}
2015-08-18 07:55:09 +00:00
if err != nil {
2023-01-11 16:50:14 +00:00
return nil , fmt . Errorf ( "couldn't fetch token: %w" , err )
2015-08-18 07:55:09 +00:00
}
2019-01-04 18:06:46 +00:00
changed = changed || ( * token != * ts . token )
2016-05-23 17:03:22 +00:00
ts . token = token
if changed {
2016-08-08 18:02:27 +00:00
// Bump on the expiry timer if it is set
if ts . expiryTimer != nil {
ts . expiryTimer . Reset ( ts . timeToExpiry ( ) )
}
2018-05-14 17:06:57 +00:00
err = PutToken ( ts . name , ts . m , token , false )
2015-09-22 06:31:12 +00:00
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "couldn't store token: %w" , err )
2015-09-22 06:31:12 +00:00
}
2015-08-18 07:55:09 +00:00
}
return token , nil
}
2016-05-23 17:03:22 +00:00
// Invalidate invalidates the token
func ( ts * TokenSource ) Invalidate ( ) {
ts . mu . Lock ( )
ts . token . AccessToken = ""
ts . mu . Unlock ( )
}
2021-03-03 10:33:29 +00:00
// Expire marks the token as expired
//
// This also marks the token in the config file as expired, if it is the same one
func ( ts * TokenSource ) Expire ( ) error {
ts . mu . Lock ( )
defer ts . mu . Unlock ( )
ts . token . Expiry = time . Now ( ) . Add ( time . Hour * ( - 1 ) ) // expire token
t , err := GetToken ( ts . name , ts . m )
if err != nil {
return err
}
if t . AccessToken == ts . token . AccessToken {
err = PutToken ( ts . name , ts . m , ts . token , false )
}
return err
}
2016-08-08 18:02:27 +00:00
// timeToExpiry returns how long until the token expires
//
// Call with the lock held
func ( ts * TokenSource ) timeToExpiry ( ) time . Duration {
t := ts . token
if t == nil {
return 0
}
if t . Expiry . IsZero ( ) {
2019-09-05 12:59:06 +00:00
return 3e9 * time . Second // ~95 years
2016-08-08 18:02:27 +00:00
}
2022-06-08 20:25:17 +00:00
return time . Until ( t . Expiry )
2016-08-08 18:02:27 +00:00
}
// OnExpiry returns a channel which has the time written to it when
// the token expires. Note that there is only one channel so if
// attaching multiple go routines it will only signal to one of them.
func ( ts * TokenSource ) OnExpiry ( ) <- chan time . Time {
ts . mu . Lock ( )
defer ts . mu . Unlock ( )
if ts . expiryTimer == nil {
ts . expiryTimer = time . NewTimer ( ts . timeToExpiry ( ) )
}
return ts . expiryTimer . C
}
2015-08-18 07:55:09 +00:00
// Check interface satisfied
2016-05-23 17:03:22 +00:00
var _ oauth2 . TokenSource = ( * TokenSource ) ( nil )
2015-08-18 07:55:09 +00:00
// Context returns a context with our HTTP Client baked in for oauth2
2020-11-05 18:02:26 +00:00
func Context ( ctx context . Context , client * http . Client ) context . Context {
return context . WithValue ( ctx , oauth2 . HTTPClient , client )
2015-08-18 07:55:09 +00:00
}
2015-09-16 19:08:40 +00:00
// overrideCredentials sets the ClientID and ClientSecret from the
2016-01-04 15:13:36 +00:00
// config file if they are not blank.
// If any value is overridden, true is returned.
2017-01-16 15:10:08 +00:00
// the origConfig is copied
2018-05-14 17:06:57 +00:00
func overrideCredentials ( name string , m configmap . Mapper , origConfig * oauth2 . Config ) ( newConfig * oauth2 . Config , changed bool ) {
2018-01-12 16:30:54 +00:00
newConfig = new ( oauth2 . Config )
* newConfig = * origConfig
2017-01-16 15:10:08 +00:00
changed = false
2018-05-14 17:06:57 +00:00
ClientID , ok := m . Get ( config . ConfigClientID )
if ok && ClientID != "" {
2018-01-12 16:30:54 +00:00
newConfig . ClientID = ClientID
2016-01-04 15:13:36 +00:00
changed = true
2015-09-16 19:08:40 +00:00
}
2018-05-14 17:06:57 +00:00
ClientSecret , ok := m . Get ( config . ConfigClientSecret )
if ok && ClientSecret != "" {
2018-01-12 16:30:54 +00:00
newConfig . ClientSecret = ClientSecret
2016-01-04 15:13:36 +00:00
changed = true
2015-09-16 19:08:40 +00:00
}
2018-05-14 17:06:57 +00:00
AuthURL , ok := m . Get ( config . ConfigAuthURL )
if ok && AuthURL != "" {
2018-01-12 16:30:54 +00:00
newConfig . Endpoint . AuthURL = AuthURL
2017-06-08 19:35:32 +00:00
changed = true
}
2018-05-14 17:06:57 +00:00
TokenURL , ok := m . Get ( config . ConfigTokenURL )
if ok && TokenURL != "" {
2018-01-12 16:30:54 +00:00
newConfig . Endpoint . TokenURL = TokenURL
2017-06-08 19:35:32 +00:00
changed = true
}
2018-01-12 16:30:54 +00:00
return newConfig , changed
2015-09-16 19:08:40 +00:00
}
2017-11-24 09:07:03 +00:00
// 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
// httpClient passed in as the base client.
2020-11-05 18:02:26 +00:00
func NewClientWithBaseClient ( ctx context . Context , name string , m configmap . Mapper , config * oauth2 . Config , baseClient * http . Client ) ( * http . Client , * TokenSource , error ) {
2018-05-14 17:06:57 +00:00
config , _ = overrideCredentials ( name , m , config )
token , err := GetToken ( name , m )
2015-08-18 07:55:09 +00:00
if err != nil {
2016-05-23 17:03:22 +00:00
return nil , nil , err
2015-08-18 07:55:09 +00:00
}
// Set our own http client in the context
2020-11-05 18:02:26 +00:00
ctx = Context ( ctx , baseClient )
2015-08-18 07:55:09 +00:00
// Wrap the TokenSource in our TokenSource which saves changed
// tokens in the config file
2016-05-23 17:03:22 +00:00
ts := & TokenSource {
name : name ,
2018-05-14 17:06:57 +00:00
m : m ,
2016-05-23 17:03:22 +00:00
token : token ,
config : config ,
ctx : ctx ,
2015-08-18 07:55:09 +00:00
}
2016-05-23 17:03:22 +00:00
return oauth2 . NewClient ( ctx , ts ) , ts , nil
2015-08-18 07:55:09 +00:00
}
2017-11-24 09:07:03 +00:00
// NewClient 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
2020-11-05 18:02:26 +00:00
func NewClient ( ctx context . Context , name string , m configmap . Mapper , oauthConfig * oauth2 . Config ) ( * http . Client , * TokenSource , error ) {
2020-11-13 15:24:43 +00:00
return NewClientWithBaseClient ( ctx , name , m , oauthConfig , fshttp . NewClient ( ctx ) )
2017-11-24 09:07:03 +00:00
}
2019-08-30 10:52:03 +00:00
// AuthResult is returned from the web server after authorization
// success or failure
type AuthResult struct {
OK bool // Failure or Success?
Name string
Description string
Code string
HelpURL string
Form url . Values // the complete contents of the form
Err error // any underlying error to report
}
// Error satisfies the error interface so AuthResult can be used as an error
func ( ar * AuthResult ) Error ( ) string {
status := "Error"
if ar . OK {
status = "OK"
}
return fmt . Sprintf ( "%s: %s\nCode: %q\nDescription: %s\nHelp: %s" ,
status , ar . Name , ar . Code , ar . Description , ar . HelpURL )
}
// CheckAuthFn is called when a good Auth has been received
type CheckAuthFn func ( * oauth2 . Config , * AuthResult ) error
2020-05-25 14:06:08 +00:00
// Options for the oauth config
type Options struct {
2021-04-29 08:28:18 +00:00
OAuth2Config * oauth2 . Config // Basic config for oauth2
2020-05-25 14:12:25 +00:00
NoOffline bool // If set then "access_type=offline" parameter is not passed
CheckAuth CheckAuthFn // When the AuthResult is known the checkAuth function is called if set
OAuth2Opts [ ] oauth2 . AuthCodeOption // extra oauth2 options
StateBlankOK bool // If set, state returned as "" is deemed to be OK
2017-06-14 15:46:46 +00:00
}
2021-04-29 08:28:18 +00:00
// ConfigOut returns a config item suitable for the backend config
2020-05-25 14:06:08 +00:00
//
2021-04-29 08:28:18 +00:00
// state is the place to return the config to
// oAuth is the config to run the oauth with
func ConfigOut ( state string , oAuth * Options ) ( * fs . ConfigOut , error ) {
return & fs . ConfigOut {
State : state ,
OAuth : oAuth ,
} , nil
}
2016-01-04 15:13:36 +00:00
2021-04-29 08:28:18 +00:00
// ConfigOAuth does the oauth config specified in the config block
//
// This is called with a state which has pushed on it
//
2022-08-05 15:35:41 +00:00
// state prefixed with "*oauth"
// state for oauth to return to
// state that returned the OAuth when we wish to recall it
// value that returned the OAuth
2021-04-29 08:28:18 +00:00
func ConfigOAuth ( ctx context . Context , name string , m configmap . Mapper , ri * fs . RegInfo , in fs . ConfigIn ) ( * fs . ConfigOut , error ) {
stateParams , state := fs . StatePop ( in . State )
// Make the next state
newState := func ( state string ) string {
return fs . StatePush ( stateParams , state )
}
// Recall the Oauth state again by calling the Config with the same input again
getOAuth := func ( ) ( opt * Options , err error ) {
tmpState , _ := fs . StatePop ( stateParams )
tmpState , State := fs . StatePop ( tmpState )
_ , Result := fs . StatePop ( tmpState )
out , err := ri . Config ( ctx , name , m , fs . ConfigIn { State : State , Result : Result } )
if err != nil {
return nil , err
2015-08-18 07:55:09 +00:00
}
2021-04-29 08:28:18 +00:00
if out . OAuth == nil {
return nil , errors . New ( "failed to recall OAuth state" )
}
opt , ok := out . OAuth . ( * Options )
if ! ok {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "internal error: oauth failed: wrong type in config: %T" , out . OAuth )
2021-04-29 08:28:18 +00:00
}
if opt . OAuth2Config == nil {
return nil , errors . New ( "internal error: oauth failed: OAuth2Config not set" )
}
return opt , nil
2015-08-18 07:55:09 +00:00
}
2021-04-29 08:28:18 +00:00
switch state {
case "*oauth" :
// See if already have a token
tokenString , ok := m . Get ( "token" )
if ok && tokenString != "" {
2021-05-04 11:27:50 +00:00
return fs . ConfigConfirm ( newState ( "*oauth-confirm" ) , true , "config_refresh_token" , "Already have a token - refresh?" )
2019-08-30 10:52:03 +00:00
}
2021-04-29 08:28:18 +00:00
return fs . ConfigGoto ( newState ( "*oauth-confirm" ) )
case "*oauth-confirm" :
if in . Result == "false" {
return fs . ConfigGoto ( newState ( "*oauth-done" ) )
2019-08-30 10:52:03 +00:00
}
2022-12-07 19:44:28 +00:00
return fs . ConfigConfirm ( newState ( "*oauth-islocal" ) , true , "config_is_local" , "Use web browser to automatically authenticate rclone with remote?\n * Say Y if the machine running rclone has a web browser you can use\n * Say N if running rclone on a (remote) machine without web browser access\nIf not sure try Y. If Y failed, try N.\n" )
2021-04-29 08:28:18 +00:00
case "*oauth-islocal" :
if in . Result == "true" {
return fs . ConfigGoto ( newState ( "*oauth-do" ) )
2018-05-13 16:28:09 +00:00
}
2021-04-29 08:28:18 +00:00
return fs . ConfigGoto ( newState ( "*oauth-remote" ) )
case "*oauth-remote" :
opt , err := getOAuth ( )
if err != nil {
return nil , err
2016-01-04 15:13:36 +00:00
}
2021-04-29 08:28:18 +00:00
if noWebserverNeeded ( opt . OAuth2Config ) {
authURL , _ , err := getAuthURL ( name , m , opt . OAuth2Config , opt )
if err != nil {
return nil , err
}
2021-05-04 11:27:50 +00:00
return fs . ConfigInput ( newState ( "*oauth-do" ) , "config_verification_code" , fmt . Sprintf ( "Verification code\n\nGo to this URL, authenticate then paste the code here.\n\n%s\n" , authURL ) )
2021-04-29 08:28:18 +00:00
}
var out strings . Builder
fmt . Fprintf ( & out , ` For this to work , you will need rclone available on a machine that has
2020-05-04 09:40:26 +00:00
a web browser available .
For more help and alternate methods see : https : //rclone.org/remote_setup/
Execute the following on the machine with the web browser ( same rclone
version recommended ) :
` )
2021-04-29 08:28:18 +00:00
// Find the overridden options
inM := ri . Options . NonDefault ( m )
delete ( inM , fs . ConfigToken ) // delete token as we are refreshing it
for k , v := range inM {
fs . Debugf ( nil , "sending %s = %q" , k , v )
}
// Encode them into a string
mCopyString , err := inM . Encode ( )
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "oauthutil authorize encode: %w" , err )
2021-04-29 08:28:18 +00:00
}
// Write what the user has to do
if len ( mCopyString ) > 0 {
fmt . Fprintf ( & out , "\trclone authorize %q %q\n" , ri . Name , mCopyString )
} else {
fmt . Fprintf ( & out , "\trclone authorize %q\n" , ri . Name )
}
fmt . Fprintln ( & out , "\nThen paste the result." )
2021-05-04 11:27:50 +00:00
return fs . ConfigInput ( newState ( "*oauth-authorize" ) , "config_token" , out . String ( ) )
2021-04-29 08:28:18 +00:00
case "*oauth-authorize" :
// Read the updates to the config
outM := configmap . Simple { }
token := oauth2 . Token { }
code := in . Result
newFormat := true
err := outM . Decode ( code )
if err != nil {
newFormat = false
err = json . Unmarshal ( [ ] byte ( code ) , & token )
}
if err != nil {
return fs . ConfigError ( newState ( "*oauth-authorize" ) , fmt . Sprintf ( "Couldn't decode response - try again (make sure you are using a matching version of rclone on both sides: %v\n" , err ) )
}
// Save the config updates
if newFormat {
for k , v := range outM {
m . Set ( k , v )
fs . Debugf ( nil , "received %s = %q" , k , v )
2021-04-04 13:56:42 +00:00
}
2021-04-29 08:28:18 +00:00
} else {
m . Set ( fs . ConfigToken , code )
}
return fs . ConfigGoto ( newState ( "*oauth-done" ) )
case "*oauth-do" :
2023-02-24 15:08:38 +00:00
// Make sure we can read the HTML template file if it was specified.
configTemplateFile , _ := m . Get ( "config_template_file" )
configTemplateString , _ := m . Get ( "config_template" )
if configTemplateFile != "" {
dat , err := os . ReadFile ( configTemplateFile )
if err != nil {
return nil , fmt . Errorf ( "failed to read template file: %w" , err )
}
templateString = string ( dat )
} else if configTemplateString != "" {
templateString = configTemplateString
} else {
templateString = DefaultAuthResponseTemplate
}
2021-04-29 08:28:18 +00:00
code := in . Result
opt , err := getOAuth ( )
if err != nil {
return nil , err
}
oauthConfig , changed := overrideCredentials ( name , m , opt . OAuth2Config )
if changed {
fs . Logf ( nil , "Make sure your Redirect URL is set to %q in your custom config.\n" , oauthConfig . RedirectURL )
}
if code == "" {
oauthConfig = fixRedirect ( oauthConfig )
code , err = configSetup ( ctx , ri . Name , name , m , oauthConfig , opt )
2021-04-04 13:56:42 +00:00
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "config failed to refresh token: %w" , err )
2016-01-04 15:13:36 +00:00
}
}
2021-04-29 08:28:18 +00:00
err = configExchange ( ctx , name , m , oauthConfig , code )
if err != nil {
return nil , err
}
return fs . ConfigGoto ( newState ( "*oauth-done" ) )
case "*oauth-done" :
// Return to the state indicated in the State stack
_ , returnState := fs . StatePop ( stateParams )
return fs . ConfigGoto ( returnState )
2015-09-12 13:17:39 +00:00
}
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "unknown internal oauth state %q" , state )
2021-04-29 08:28:18 +00:00
}
func init ( ) {
// Set the function to avoid circular import
fs . ConfigOAuth = ConfigOAuth
}
// Return true if can run without a webserver and just entering a code
func noWebserverNeeded ( oauthConfig * oauth2 . Config ) bool {
return oauthConfig . RedirectURL == TitleBarRedirectURL
}
// get the URL we need to send the user to
func getAuthURL ( name string , m configmap . Mapper , oauthConfig * oauth2 . Config , opt * Options ) ( authURL string , state string , err error ) {
oauthConfig , _ = overrideCredentials ( name , m , oauthConfig )
2015-09-12 13:17:39 +00:00
2015-09-02 22:37:42 +00:00
// Make random state
2021-04-29 08:28:18 +00:00
state , err = random . Password ( 128 )
2015-09-02 22:37:42 +00:00
if err != nil {
2021-04-29 08:28:18 +00:00
return "" , "" , err
2015-09-02 22:37:42 +00:00
}
2019-08-30 10:52:03 +00:00
// Generate oauth URL
2020-05-25 14:06:08 +00:00
opts := opt . OAuth2Opts
if ! opt . NoOffline {
2017-06-14 15:46:46 +00:00
opts = append ( opts , oauth2 . AccessTypeOffline )
}
2021-04-29 08:28:18 +00:00
authURL = oauthConfig . AuthCodeURL ( state , opts ... )
return authURL , state , nil
}
2015-09-02 22:37:42 +00:00
2021-04-29 08:28:18 +00:00
// If TitleBarRedirect is set but we are doing a real oauth, then
// override our redirect URL
func fixRedirect ( oauthConfig * oauth2 . Config ) * oauth2 . Config {
switch oauthConfig . RedirectURL {
case TitleBarRedirectURL :
// copy the config and set to use the internal webserver
configCopy := * oauthConfig
oauthConfig = & configCopy
oauthConfig . RedirectURL = RedirectURL
2015-09-02 22:37:42 +00:00
}
2021-04-29 08:28:18 +00:00
return oauthConfig
}
2015-09-02 22:37:42 +00:00
2021-04-29 08:28:18 +00:00
// configSetup does the initial creation of the token
//
2022-08-05 15:35:41 +00:00
// If opt is nil it will use the default Options.
2021-04-29 08:28:18 +00:00
//
// It will run an internal webserver to receive the results
func configSetup ( ctx context . Context , id , name string , m configmap . Mapper , oauthConfig * oauth2 . Config , opt * Options ) ( string , error ) {
if opt == nil {
opt = & Options { }
}
authorizeNoAutoBrowserValue , ok := m . Get ( config . ConfigAuthNoBrowser )
authorizeNoAutoBrowser := ok && authorizeNoAutoBrowserValue != ""
authURL , state , err := getAuthURL ( name , m , oauthConfig , opt )
if err != nil {
return "" , err
}
// Prepare webserver
server := newAuthServer ( opt , bindAddress , state , authURL )
err = server . Init ( )
if err != nil {
2021-11-04 10:12:57 +00:00
return "" , fmt . Errorf ( "failed to start auth webserver: %w" , err )
2021-04-29 08:28:18 +00:00
}
go server . Serve ( )
defer server . Stop ( )
authURL = "http://" + bindAddress + "/auth?state=" + state
if ! authorizeNoAutoBrowser {
2019-10-26 19:19:22 +00:00
// Open the URL for the user to visit
_ = open . Start ( authURL )
2021-04-29 08:28:18 +00:00
fs . Logf ( nil , "If your browser doesn't open automatically go to the following link: %s\n" , authURL )
2019-10-26 19:19:22 +00:00
} else {
2021-04-29 08:28:18 +00:00
fs . Logf ( nil , "Please go to the following link: %s\n" , authURL )
2019-10-26 19:19:22 +00:00
}
2021-04-29 08:28:18 +00:00
fs . Logf ( nil , "Log in and authorize rclone for access\n" )
2015-08-18 07:55:09 +00:00
2021-04-29 08:28:18 +00:00
// Read the code via the webserver
fs . Logf ( nil , "Waiting for code...\n" )
auth := <- server . result
if ! auth . OK || auth . Code == "" {
return "" , auth
}
fs . Logf ( nil , "Got code\n" )
if opt . CheckAuth != nil {
err = opt . CheckAuth ( oauthConfig , auth )
if err != nil {
return "" , err
2019-08-30 10:52:03 +00:00
}
2015-09-11 12:26:51 +00:00
}
2021-04-29 08:28:18 +00:00
return auth . Code , nil
}
2019-08-30 10:52:03 +00:00
2021-04-29 08:28:18 +00:00
// Exchange the code for a token
func configExchange ( ctx context . Context , name string , m configmap . Mapper , oauthConfig * oauth2 . Config , code string ) error {
2020-11-13 15:24:43 +00:00
ctx = Context ( ctx , fshttp . NewClient ( ctx ) )
2021-04-29 08:28:18 +00:00
token , err := oauthConfig . Exchange ( ctx , code )
2015-08-18 07:55:09 +00:00
if err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "failed to get token: %w" , err )
2015-08-18 07:55:09 +00:00
}
2018-05-14 17:06:57 +00:00
return PutToken ( name , m , token , true )
2015-08-18 07:55:09 +00:00
}
2015-09-02 22:37:42 +00:00
// Local web server for collecting auth
type authServer struct {
2020-05-25 14:06:08 +00:00
opt * Options
2019-08-30 10:52:03 +00:00
state string
listener net . Listener
bindAddress string
authURL string
server * http . Server
result chan * AuthResult
2018-06-01 10:34:35 +00:00
}
2019-08-30 10:52:03 +00:00
// newAuthServer makes the webserver for collecting auth
2020-05-25 14:06:08 +00:00
func newAuthServer ( opt * Options , bindAddress , state , authURL string ) * authServer {
2019-08-30 10:52:03 +00:00
return & authServer {
2020-05-25 14:06:08 +00:00
opt : opt ,
2019-08-30 10:52:03 +00:00
state : state ,
bindAddress : bindAddress ,
authURL : authURL , // http://host/auth redirects to here
result : make ( chan * AuthResult , 1 ) ,
}
2015-09-02 22:37:42 +00:00
}
2019-08-30 10:52:03 +00:00
// Receive the auth request
func ( s * authServer ) handleAuth ( w http . ResponseWriter , req * http . Request ) {
2021-11-24 13:08:25 +00:00
if req . URL . Path != "/" {
fs . Debugf ( nil , "Ignoring %s request on auth server to %q" , req . Method , req . URL . Path )
http . NotFound ( w , req )
return
}
2019-08-30 10:52:03 +00:00
fs . Debugf ( nil , "Received %s request on auth server to %q" , req . Method , req . URL . Path )
// Reply with the response to the user and to the channel
reply := func ( status int , res * AuthResult ) {
w . WriteHeader ( status )
w . Header ( ) . Set ( "Content-Type" , "text/html" )
2023-02-24 15:08:38 +00:00
var t = template . Must ( template . New ( "authResponse" ) . Parse ( templateString ) )
2019-08-30 10:52:03 +00:00
if err := t . Execute ( w , res ) ; err != nil {
fs . Debugf ( nil , "Could not execute template for web response." )
}
s . result <- res
}
// Parse the form parameters and save them
err := req . ParseForm ( )
if err != nil {
reply ( http . StatusBadRequest , & AuthResult {
Name : "Parse form error" ,
Description : err . Error ( ) ,
} )
return
}
// get code, error if empty
code := req . Form . Get ( "code" )
if code == "" {
reply ( http . StatusBadRequest , & AuthResult {
Name : "Auth Error" ,
Description : "No code returned by remote server" ,
} )
return
}
// check state
state := req . Form . Get ( "state" )
2020-05-25 14:12:25 +00:00
if state != s . state && ! ( state == "" && s . opt . StateBlankOK ) {
2019-08-30 10:52:03 +00:00
reply ( http . StatusBadRequest , & AuthResult {
Name : "Auth state doesn't match" ,
Description : fmt . Sprintf ( "Expecting %q got %q" , s . state , state ) ,
} )
return
}
// code OK
reply ( http . StatusOK , & AuthResult {
OK : true ,
Code : code ,
Form : req . Form ,
} )
2018-06-03 21:49:11 +00:00
}
2019-09-27 13:59:56 +00:00
// Init gets the internal web server ready to receive config details
func ( s * authServer ) Init ( ) error {
2017-02-09 11:01:20 +00:00
fs . Debugf ( nil , "Starting auth server on %s" , s . bindAddress )
2015-09-02 22:37:42 +00:00
mux := http . NewServeMux ( )
2017-08-03 18:08:31 +00:00
s . server = & http . Server {
2015-09-02 22:37:42 +00:00
Addr : s . bindAddress ,
Handler : mux ,
}
2017-08-03 18:08:31 +00:00
s . server . SetKeepAlivesEnabled ( false )
2019-08-30 10:52:03 +00:00
2015-09-11 12:26:51 +00:00
mux . HandleFunc ( "/auth" , func ( w http . ResponseWriter , req * http . Request ) {
2019-09-27 13:59:56 +00:00
state := req . FormValue ( "state" )
if state != s . state {
fs . Debugf ( nil , "State did not match: want %q got %q" , s . state , state )
2019-08-30 10:52:03 +00:00
http . Error ( w , "State did not match - please try again" , http . StatusForbidden )
2019-09-27 13:59:56 +00:00
return
}
2021-02-11 16:29:52 +00:00
fs . Debugf ( nil , "Redirecting browser to: %s" , s . authURL )
2016-03-23 13:00:43 +00:00
http . Redirect ( w , req , s . authURL , http . StatusTemporaryRedirect )
2015-09-11 12:26:51 +00:00
} )
2019-08-30 10:52:03 +00:00
mux . HandleFunc ( "/" , s . handleAuth )
2015-09-02 22:37:42 +00:00
var err error
s . listener , err = net . Listen ( "tcp" , s . bindAddress )
if err != nil {
2019-09-27 13:59:56 +00:00
return err
2015-09-02 22:37:42 +00:00
}
2019-09-27 13:59:56 +00:00
return nil
}
// Serve the auth server, doesn't return
func ( s * authServer ) Serve ( ) {
err := s . server . Serve ( s . listener )
2017-02-09 11:01:20 +00:00
fs . Debugf ( nil , "Closed auth server with error: %v" , err )
2015-09-02 22:37:42 +00:00
}
2019-04-15 19:03:33 +00:00
2019-09-27 13:59:56 +00:00
// Stop the auth server by closing its socket
2019-04-15 19:03:33 +00:00
func ( s * authServer ) Stop ( ) {
fs . Debugf ( nil , "Closing auth server" )
2019-08-30 10:52:03 +00:00
close ( s . result )
2019-04-15 19:03:33 +00:00
_ = s . listener . Close ( )
// close the server
_ = s . server . Close ( )
}