mirror of
https://github.com/rclone/rclone.git
synced 2025-12-06 00:03:32 +00:00
Compare commits
3 Commits
dependabot
...
pr-6343-on
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1f085d2a5 | ||
|
|
37d85d2576 | ||
|
|
e612310296 |
@@ -46,7 +46,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/random"
|
"github.com/rclone/rclone/lib/random"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"github.com/youmark/pkcs8"
|
"github.com/youmark/pkcs8"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -65,12 +64,10 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: nil,
|
Scopes: nil,
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://app.box.com/api/oauth2/authorize",
|
||||||
AuthURL: "https://app.box.com/api/oauth2/authorize",
|
TokenURL: "https://app.box.com/api/oauth2/token",
|
||||||
TokenURL: "https://app.box.com/api/oauth2/token",
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
|
|||||||
@@ -80,9 +80,10 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
driveConfig = &oauth2.Config{
|
driveConfig = &oauthutil.Config{
|
||||||
Scopes: []string{scopePrefix + "drive"},
|
Scopes: []string{scopePrefix + "drive"},
|
||||||
Endpoint: google.Endpoint,
|
AuthURL: google.Endpoint.AuthURL,
|
||||||
|
TokenURL: google.Endpoint.TokenURL,
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
dropboxConfig = &oauth2.Config{
|
dropboxConfig = &oauthutil.Config{
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"files.metadata.write",
|
"files.metadata.write",
|
||||||
"files.content.write",
|
"files.content.write",
|
||||||
@@ -109,7 +109,8 @@ var (
|
|||||||
// AuthURL: "https://www.dropbox.com/1/oauth2/authorize",
|
// AuthURL: "https://www.dropbox.com/1/oauth2/authorize",
|
||||||
// TokenURL: "https://api.dropboxapi.com/1/oauth2/token",
|
// TokenURL: "https://api.dropboxapi.com/1/oauth2/token",
|
||||||
// },
|
// },
|
||||||
Endpoint: dropbox.OAuthEndpoint(""),
|
AuthURL: dropbox.OAuthEndpoint("").AuthURL,
|
||||||
|
TokenURL: dropbox.OAuthEndpoint("").TokenURL,
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -134,7 +135,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Gets an oauth config with the right scopes
|
// Gets an oauth config with the right scopes
|
||||||
func getOauthConfig(m configmap.Mapper) *oauth2.Config {
|
func getOauthConfig(m configmap.Mapper) *oauthutil.Config {
|
||||||
// If not impersonating, use standard scopes
|
// If not impersonating, use standard scopes
|
||||||
if impersonate, _ := m.Get("impersonate"); impersonate == "" {
|
if impersonate, _ := m.Get("impersonate"); impersonate == "" {
|
||||||
return dropboxConfig
|
return dropboxConfig
|
||||||
|
|||||||
@@ -60,14 +60,17 @@ const (
|
|||||||
minSleep = 10 * time.Millisecond
|
minSleep = 10 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// Description of how to auth for this app
|
var (
|
||||||
var storageConfig = &oauth2.Config{
|
// Description of how to auth for this app
|
||||||
Scopes: []string{storage.DevstorageReadWriteScope},
|
storageConfig = &oauthutil.Config{
|
||||||
Endpoint: google.Endpoint,
|
Scopes: []string{storage.DevstorageReadWriteScope},
|
||||||
ClientID: rcloneClientID,
|
AuthURL: google.Endpoint.AuthURL,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
TokenURL: google.Endpoint.TokenURL,
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
ClientID: rcloneClientID,
|
||||||
}
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/oauthutil"
|
"github.com/rclone/rclone/lib/oauthutil"
|
||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,13 +59,14 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"openid",
|
"openid",
|
||||||
"profile",
|
"profile",
|
||||||
scopeReadWrite, // this must be at position scopeAccess
|
scopeReadWrite, // this must be at position scopeAccess
|
||||||
},
|
},
|
||||||
Endpoint: google.Endpoint,
|
AuthURL: google.Endpoint.AuthURL,
|
||||||
|
TokenURL: google.Endpoint.TokenURL,
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/oauthutil"
|
"github.com/rclone/rclone/lib/oauthutil"
|
||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -48,11 +47,9 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app.
|
// Description of how to auth for this app.
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://my.hidrive.com/client/authorize",
|
||||||
AuthURL: "https://my.hidrive.com/client/authorize",
|
TokenURL: "https://my.hidrive.com/oauth2/token",
|
||||||
TokenURL: "https://my.hidrive.com/oauth2/token",
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.TitleBarRedirectURL,
|
RedirectURL: oauthutil.TitleBarRedirectURL,
|
||||||
|
|||||||
@@ -277,11 +277,9 @@ machines.`)
|
|||||||
m.Set(configClientID, teliaseCloudClientID)
|
m.Set(configClientID, teliaseCloudClientID)
|
||||||
m.Set(configTokenURL, teliaseCloudTokenURL)
|
m.Set(configTokenURL, teliaseCloudTokenURL)
|
||||||
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
||||||
OAuth2Config: &oauth2.Config{
|
OAuth2Config: &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: teliaseCloudAuthURL,
|
||||||
AuthURL: teliaseCloudAuthURL,
|
TokenURL: teliaseCloudTokenURL,
|
||||||
TokenURL: teliaseCloudTokenURL,
|
|
||||||
},
|
|
||||||
ClientID: teliaseCloudClientID,
|
ClientID: teliaseCloudClientID,
|
||||||
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -292,11 +290,9 @@ machines.`)
|
|||||||
m.Set(configClientID, telianoCloudClientID)
|
m.Set(configClientID, telianoCloudClientID)
|
||||||
m.Set(configTokenURL, telianoCloudTokenURL)
|
m.Set(configTokenURL, telianoCloudTokenURL)
|
||||||
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
||||||
OAuth2Config: &oauth2.Config{
|
OAuth2Config: &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: telianoCloudAuthURL,
|
||||||
AuthURL: telianoCloudAuthURL,
|
TokenURL: telianoCloudTokenURL,
|
||||||
TokenURL: telianoCloudTokenURL,
|
|
||||||
},
|
|
||||||
ClientID: telianoCloudClientID,
|
ClientID: telianoCloudClientID,
|
||||||
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -307,11 +303,9 @@ machines.`)
|
|||||||
m.Set(configClientID, tele2CloudClientID)
|
m.Set(configClientID, tele2CloudClientID)
|
||||||
m.Set(configTokenURL, tele2CloudTokenURL)
|
m.Set(configTokenURL, tele2CloudTokenURL)
|
||||||
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
||||||
OAuth2Config: &oauth2.Config{
|
OAuth2Config: &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: tele2CloudAuthURL,
|
||||||
AuthURL: tele2CloudAuthURL,
|
TokenURL: tele2CloudTokenURL,
|
||||||
TokenURL: tele2CloudTokenURL,
|
|
||||||
},
|
|
||||||
ClientID: tele2CloudClientID,
|
ClientID: tele2CloudClientID,
|
||||||
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -322,11 +316,9 @@ machines.`)
|
|||||||
m.Set(configClientID, onlimeCloudClientID)
|
m.Set(configClientID, onlimeCloudClientID)
|
||||||
m.Set(configTokenURL, onlimeCloudTokenURL)
|
m.Set(configTokenURL, onlimeCloudTokenURL)
|
||||||
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
|
||||||
OAuth2Config: &oauth2.Config{
|
OAuth2Config: &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: onlimeCloudAuthURL,
|
||||||
AuthURL: onlimeCloudAuthURL,
|
TokenURL: onlimeCloudTokenURL,
|
||||||
TokenURL: onlimeCloudTokenURL,
|
|
||||||
},
|
|
||||||
ClientID: onlimeCloudClientID,
|
ClientID: onlimeCloudClientID,
|
||||||
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
Scopes: []string{"openid", "jotta-default", "offline_access"},
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -924,19 +916,17 @@ func getOAuthClient(ctx context.Context, name string, m configmap.Mapper) (oAuth
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseClient := fshttp.NewClient(ctx)
|
baseClient := fshttp.NewClient(ctx)
|
||||||
oauthConfig := &oauth2.Config{
|
oauthConfig := &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: defaultTokenURL,
|
||||||
AuthURL: defaultTokenURL,
|
TokenURL: defaultTokenURL,
|
||||||
TokenURL: defaultTokenURL,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if ver == configVersion {
|
if ver == configVersion {
|
||||||
oauthConfig.ClientID = defaultClientID
|
oauthConfig.ClientID = defaultClientID
|
||||||
// if custom endpoints are set use them else stick with defaults
|
// if custom endpoints are set use them else stick with defaults
|
||||||
if tokenURL, ok := m.Get(configTokenURL); ok {
|
if tokenURL, ok := m.Get(configTokenURL); ok {
|
||||||
oauthConfig.Endpoint.TokenURL = tokenURL
|
oauthConfig.TokenURL = tokenURL
|
||||||
// jottacloud is weird. we need to use the tokenURL as authURL
|
// jottacloud is weird. we need to use the tokenURL as authURL
|
||||||
oauthConfig.Endpoint.AuthURL = tokenURL
|
oauthConfig.AuthURL = tokenURL
|
||||||
}
|
}
|
||||||
} else if ver == legacyConfigVersion {
|
} else if ver == legacyConfigVersion {
|
||||||
clientID, ok := m.Get(configClientID)
|
clientID, ok := m.Get(configClientID)
|
||||||
@@ -950,8 +940,8 @@ func getOAuthClient(ctx context.Context, name string, m configmap.Mapper) (oAuth
|
|||||||
oauthConfig.ClientID = clientID
|
oauthConfig.ClientID = clientID
|
||||||
oauthConfig.ClientSecret = obscure.MustReveal(clientSecret)
|
oauthConfig.ClientSecret = obscure.MustReveal(clientSecret)
|
||||||
|
|
||||||
oauthConfig.Endpoint.TokenURL = legacyTokenURL
|
oauthConfig.TokenURL = legacyTokenURL
|
||||||
oauthConfig.Endpoint.AuthURL = legacyTokenURL
|
oauthConfig.AuthURL = legacyTokenURL
|
||||||
|
|
||||||
// add the request filter to fix token refresh
|
// add the request filter to fix token refresh
|
||||||
if do, ok := baseClient.Transport.(interface {
|
if do, ok := baseClient.Transport.(interface {
|
||||||
|
|||||||
@@ -68,14 +68,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Description of how to authorize
|
// Description of how to authorize
|
||||||
var oauthConfig = &oauth2.Config{
|
var oauthConfig = &oauthutil.Config{
|
||||||
ClientID: api.OAuthClientID,
|
ClientID: api.OAuthClientID,
|
||||||
ClientSecret: "",
|
ClientSecret: "",
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: api.OAuthURL,
|
||||||
AuthURL: api.OAuthURL,
|
TokenURL: api.OAuthURL,
|
||||||
TokenURL: api.OAuthURL,
|
AuthStyle: oauth2.AuthStyleInParams,
|
||||||
AuthStyle: oauth2.AuthStyleInParams,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
@@ -438,7 +436,9 @@ func (f *Fs) authorize(ctx context.Context, force bool) (err error) {
|
|||||||
if err != nil || !tokenIsValid(t) {
|
if err != nil || !tokenIsValid(t) {
|
||||||
fs.Infof(f, "Valid token not found, authorizing.")
|
fs.Infof(f, "Valid token not found, authorizing.")
|
||||||
ctx := oauthutil.Context(ctx, f.cli)
|
ctx := oauthutil.Context(ctx, f.cli)
|
||||||
t, err = oauthConfig.PasswordCredentialsToken(ctx, f.opt.Username, f.opt.Password)
|
|
||||||
|
oauth2Conf := oauthConfig.MakeOauth2Config()
|
||||||
|
t, err = oauth2Conf.PasswordCredentialsToken(ctx, f.opt.Username, f.opt.Password)
|
||||||
}
|
}
|
||||||
if err == nil && !tokenIsValid(t) {
|
if err == nil && !tokenIsValid(t) {
|
||||||
err = errors.New("invalid token")
|
err = errors.New("invalid token")
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
"github.com/rclone/rclone/lib/readers"
|
"github.com/rclone/rclone/lib/readers"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -65,14 +64,21 @@ const (
|
|||||||
|
|
||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
authPath = "/common/oauth2/v2.0/authorize"
|
|
||||||
tokenPath = "/common/oauth2/v2.0/token"
|
// Define the paths used for token operations
|
||||||
|
commonPathPrefix = "/common" // prefix for the paths if tenant isn't known
|
||||||
|
authPath = "/oauth2/v2.0/authorize"
|
||||||
|
tokenPath = "/oauth2/v2.0/token"
|
||||||
|
|
||||||
scopeAccess = fs.SpaceSepList{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "Sites.Read.All", "offline_access"}
|
scopeAccess = fs.SpaceSepList{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "Sites.Read.All", "offline_access"}
|
||||||
scopeAccessWithoutSites = fs.SpaceSepList{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "offline_access"}
|
scopeAccessWithoutSites = fs.SpaceSepList{"Files.Read", "Files.ReadWrite", "Files.Read.All", "Files.ReadWrite.All", "offline_access"}
|
||||||
|
|
||||||
// Description of how to auth for this app for a business account
|
// When using client credential OAuth flow, scope of .default is required in order
|
||||||
oauthConfig = &oauth2.Config{
|
// to use the permissions configured for the application within the tenant
|
||||||
|
scopeAccessClientCred = fs.SpaceSepList{".default"}
|
||||||
|
|
||||||
|
// Base config for how to auth
|
||||||
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: scopeAccess,
|
Scopes: scopeAccess,
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
@@ -183,6 +189,14 @@ Choose or manually enter a custom space separated list with all scopes, that rcl
|
|||||||
Help: "Read and write access to all resources, without the ability to browse SharePoint sites. \nSame as if disable_site_permission was set to true",
|
Help: "Read and write access to all resources, without the ability to browse SharePoint sites. \nSame as if disable_site_permission was set to true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
Name: "tenant",
|
||||||
|
Help: `ID of the service principal's tenant. Also called its directory ID.
|
||||||
|
|
||||||
|
Set this if using
|
||||||
|
- Client Credential flow
|
||||||
|
`,
|
||||||
|
Sensitive: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "disable_site_permission",
|
Name: "disable_site_permission",
|
||||||
Help: `Disable the request for Sites.Read.All permission.
|
Help: `Disable the request for Sites.Read.All permission.
|
||||||
@@ -527,28 +541,54 @@ func chooseDrive(ctx context.Context, name string, m configmap.Mapper, srv *rest
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config the backend
|
// Make the oauth config for the backend
|
||||||
func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
|
func makeOauthConfig(ctx context.Context, opt *Options) (*oauthutil.Config, error) {
|
||||||
region, graphURL := getRegionURL(m)
|
// Copy the default oauthConfig
|
||||||
|
oauthConfig := *oauthConfig
|
||||||
|
|
||||||
if config.State == "" {
|
// Set the scopes
|
||||||
var accessScopes fs.SpaceSepList
|
oauthConfig.Scopes = opt.AccessScopes
|
||||||
accessScopesString, _ := m.Get("access_scopes")
|
if opt.DisableSitePermission {
|
||||||
err := accessScopes.Set(accessScopesString)
|
oauthConfig.Scopes = scopeAccessWithoutSites
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the auth URLs
|
||||||
|
prefix := commonPathPrefix
|
||||||
|
if opt.Tenant != "" {
|
||||||
|
prefix = "/" + opt.Tenant
|
||||||
|
}
|
||||||
|
oauthConfig.TokenURL = authEndpoint[opt.Region] + prefix + tokenPath
|
||||||
|
oauthConfig.AuthURL = authEndpoint[opt.Region] + prefix + authPath
|
||||||
|
|
||||||
|
// Check to see if we are using client credentials flow
|
||||||
|
if opt.ClientCredentials {
|
||||||
|
// Override scope to .default
|
||||||
|
oauthConfig.Scopes = scopeAccessClientCred
|
||||||
|
if opt.Tenant == "" {
|
||||||
|
return nil, fmt.Errorf("tenant parameter must be set when using %s", config.ConfigClientCredentials)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oauthConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config the backend
|
||||||
|
func Config(ctx context.Context, name string, m configmap.Mapper, conf fs.ConfigIn) (*fs.ConfigOut, error) {
|
||||||
|
opt := new(Options)
|
||||||
|
err := configstruct.Set(m, opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, graphURL := getRegionURL(m)
|
||||||
|
|
||||||
|
// Check to see if this is the start of the state machine execution
|
||||||
|
if conf.State == "" {
|
||||||
|
conf, err := makeOauthConfig(ctx, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse access_scopes: %w", err)
|
return nil, err
|
||||||
}
|
|
||||||
oauthConfig.Scopes = []string(accessScopes)
|
|
||||||
disableSitePermission, _ := m.Get("disable_site_permission")
|
|
||||||
if disableSitePermission == "true" {
|
|
||||||
oauthConfig.Scopes = scopeAccessWithoutSites
|
|
||||||
}
|
|
||||||
oauthConfig.Endpoint = oauth2.Endpoint{
|
|
||||||
AuthURL: authEndpoint[region] + authPath,
|
|
||||||
TokenURL: authEndpoint[region] + tokenPath,
|
|
||||||
}
|
}
|
||||||
return oauthutil.ConfigOut("choose_type", &oauthutil.Options{
|
return oauthutil.ConfigOut("choose_type", &oauthutil.Options{
|
||||||
OAuth2Config: oauthConfig,
|
OAuth2Config: conf,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,9 +596,11 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to configure OneDrive: %w", err)
|
return nil, fmt.Errorf("failed to configure OneDrive: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a REST client, build on the OAuth client created above
|
||||||
srv := rest.NewClient(oAuthClient)
|
srv := rest.NewClient(oAuthClient)
|
||||||
|
|
||||||
switch config.State {
|
switch conf.State {
|
||||||
case "choose_type":
|
case "choose_type":
|
||||||
return fs.ConfigChooseExclusiveFixed("choose_type_done", "config_type", "Type of connection", []fs.OptionExample{{
|
return fs.ConfigChooseExclusiveFixed("choose_type_done", "config_type", "Type of connection", []fs.OptionExample{{
|
||||||
Value: "onedrive",
|
Value: "onedrive",
|
||||||
@@ -584,7 +626,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
|
|||||||
}})
|
}})
|
||||||
case "choose_type_done":
|
case "choose_type_done":
|
||||||
// Jump to next state according to config chosen
|
// Jump to next state according to config chosen
|
||||||
return fs.ConfigGoto(config.Result)
|
return fs.ConfigGoto(conf.Result)
|
||||||
case "onedrive":
|
case "onedrive":
|
||||||
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
||||||
opts: rest.Opts{
|
opts: rest.Opts{
|
||||||
@@ -602,16 +644,22 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
case "driveid":
|
case "driveid":
|
||||||
return fs.ConfigInput("driveid_end", "config_driveid_fixed", "Drive ID")
|
out, err := fs.ConfigInput("driveid_end", "config_driveid_fixed", "Drive ID")
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
// Default the drive_id to the previous version in the config
|
||||||
|
out.Option.Default, _ = m.Get("drive_id")
|
||||||
|
return out, nil
|
||||||
case "driveid_end":
|
case "driveid_end":
|
||||||
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
||||||
finalDriveID: config.Result,
|
finalDriveID: conf.Result,
|
||||||
})
|
})
|
||||||
case "siteid":
|
case "siteid":
|
||||||
return fs.ConfigInput("siteid_end", "config_siteid", "Site ID")
|
return fs.ConfigInput("siteid_end", "config_siteid", "Site ID")
|
||||||
case "siteid_end":
|
case "siteid_end":
|
||||||
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
||||||
siteID: config.Result,
|
siteID: conf.Result,
|
||||||
})
|
})
|
||||||
case "url":
|
case "url":
|
||||||
return fs.ConfigInput("url_end", "config_site_url", `Site URL
|
return fs.ConfigInput("url_end", "config_site_url", `Site URL
|
||||||
@@ -622,7 +670,7 @@ Examples:
|
|||||||
- "https://XXX.sharepoint.com/teams/ID"
|
- "https://XXX.sharepoint.com/teams/ID"
|
||||||
`)
|
`)
|
||||||
case "url_end":
|
case "url_end":
|
||||||
siteURL := config.Result
|
siteURL := conf.Result
|
||||||
re := regexp.MustCompile(`https://.*\.sharepoint\.com(/.*)`)
|
re := regexp.MustCompile(`https://.*\.sharepoint\.com(/.*)`)
|
||||||
match := re.FindStringSubmatch(siteURL)
|
match := re.FindStringSubmatch(siteURL)
|
||||||
if len(match) == 2 {
|
if len(match) == 2 {
|
||||||
@@ -637,12 +685,12 @@ Examples:
|
|||||||
return fs.ConfigInput("path_end", "config_sharepoint_url", `Server-relative URL`)
|
return fs.ConfigInput("path_end", "config_sharepoint_url", `Server-relative URL`)
|
||||||
case "path_end":
|
case "path_end":
|
||||||
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
||||||
relativePath: config.Result,
|
relativePath: conf.Result,
|
||||||
})
|
})
|
||||||
case "search":
|
case "search":
|
||||||
return fs.ConfigInput("search_end", "config_search_term", `Search term`)
|
return fs.ConfigInput("search_end", "config_search_term", `Search term`)
|
||||||
case "search_end":
|
case "search_end":
|
||||||
searchTerm := config.Result
|
searchTerm := conf.Result
|
||||||
opts := rest.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
RootURL: graphURL,
|
RootURL: graphURL,
|
||||||
@@ -664,10 +712,10 @@ Examples:
|
|||||||
})
|
})
|
||||||
case "search_sites":
|
case "search_sites":
|
||||||
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
|
||||||
siteID: config.Result,
|
siteID: conf.Result,
|
||||||
})
|
})
|
||||||
case "driveid_final":
|
case "driveid_final":
|
||||||
finalDriveID := config.Result
|
finalDriveID := conf.Result
|
||||||
|
|
||||||
// Test the driveID and get drive type
|
// Test the driveID and get drive type
|
||||||
opts := rest.Opts{
|
opts := rest.Opts{
|
||||||
@@ -686,12 +734,12 @@ Examples:
|
|||||||
|
|
||||||
return fs.ConfigConfirm("driveid_final_end", true, "config_drive_ok", fmt.Sprintf("Drive OK?\n\nFound drive %q of type %q\nURL: %s\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL))
|
return fs.ConfigConfirm("driveid_final_end", true, "config_drive_ok", fmt.Sprintf("Drive OK?\n\nFound drive %q of type %q\nURL: %s\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL))
|
||||||
case "driveid_final_end":
|
case "driveid_final_end":
|
||||||
if config.Result == "true" {
|
if conf.Result == "true" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return fs.ConfigGoto("choose_type")
|
return fs.ConfigGoto("choose_type")
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unknown state %q", config.State)
|
return nil, fmt.Errorf("unknown state %q", conf.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options defines the configuration for this backend
|
// Options defines the configuration for this backend
|
||||||
@@ -702,7 +750,9 @@ type Options struct {
|
|||||||
DriveType string `config:"drive_type"`
|
DriveType string `config:"drive_type"`
|
||||||
RootFolderID string `config:"root_folder_id"`
|
RootFolderID string `config:"root_folder_id"`
|
||||||
DisableSitePermission bool `config:"disable_site_permission"`
|
DisableSitePermission bool `config:"disable_site_permission"`
|
||||||
|
ClientCredentials bool `config:"client_credentials"`
|
||||||
AccessScopes fs.SpaceSepList `config:"access_scopes"`
|
AccessScopes fs.SpaceSepList `config:"access_scopes"`
|
||||||
|
Tenant string `config:"tenant"`
|
||||||
ExposeOneNoteFiles bool `config:"expose_onenote_files"`
|
ExposeOneNoteFiles bool `config:"expose_onenote_files"`
|
||||||
ServerSideAcrossConfigs bool `config:"server_side_across_configs"`
|
ServerSideAcrossConfigs bool `config:"server_side_across_configs"`
|
||||||
ListChunk int64 `config:"list_chunk"`
|
ListChunk int64 `config:"list_chunk"`
|
||||||
@@ -990,13 +1040,10 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootURL := graphAPIEndpoint[opt.Region] + "/v1.0" + "/drives/" + opt.DriveID
|
rootURL := graphAPIEndpoint[opt.Region] + "/v1.0" + "/drives/" + opt.DriveID
|
||||||
oauthConfig.Scopes = opt.AccessScopes
|
|
||||||
if opt.DisableSitePermission {
|
oauthConfig, err := makeOauthConfig(ctx, opt)
|
||||||
oauthConfig.Scopes = scopeAccessWithoutSites
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
oauthConfig.Endpoint = oauth2.Endpoint{
|
|
||||||
AuthURL: authEndpoint[opt.Region] + authPath,
|
|
||||||
TokenURL: authEndpoint[opt.Region] + tokenPath,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := fshttp.NewClient(ctx)
|
client := fshttp.NewClient(ctx)
|
||||||
@@ -2563,8 +2610,11 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
|||||||
return errors.New("can't upload content to a OneNote file")
|
return errors.New("can't upload content to a OneNote file")
|
||||||
}
|
}
|
||||||
|
|
||||||
o.fs.tokenRenewer.Start()
|
// Only start the renewer if we have a valid one
|
||||||
defer o.fs.tokenRenewer.Stop()
|
if o.fs.tokenRenewer != nil {
|
||||||
|
o.fs.tokenRenewer.Start()
|
||||||
|
defer o.fs.tokenRenewer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
size := src.Size()
|
size := src.Size()
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,10 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: nil,
|
Scopes: nil,
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://my.pcloud.com/oauth2/authorize",
|
||||||
AuthURL: "https://my.pcloud.com/oauth2/authorize",
|
// TokenURL: "https://api.pcloud.com/oauth2_token", set by updateTokenURL
|
||||||
// TokenURL: "https://api.pcloud.com/oauth2_token", set by updateTokenURL
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -61,8 +59,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Update the TokenURL with the actual hostname
|
// Update the TokenURL with the actual hostname
|
||||||
func updateTokenURL(oauthConfig *oauth2.Config, hostname string) {
|
func updateTokenURL(oauthConfig *oauthutil.Config, hostname string) {
|
||||||
oauthConfig.Endpoint.TokenURL = "https://" + hostname + "/oauth2_token"
|
oauthConfig.TokenURL = "https://" + hostname + "/oauth2_token"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
@@ -79,7 +77,7 @@ func init() {
|
|||||||
fs.Errorf(nil, "Failed to read config: %v", err)
|
fs.Errorf(nil, "Failed to read config: %v", err)
|
||||||
}
|
}
|
||||||
updateTokenURL(oauthConfig, optc.Hostname)
|
updateTokenURL(oauthConfig, optc.Hostname)
|
||||||
checkAuth := func(oauthConfig *oauth2.Config, auth *oauthutil.AuthResult) error {
|
checkAuth := func(oauthConfig *oauthutil.Config, auth *oauthutil.AuthResult) error {
|
||||||
if auth == nil || auth.Form == nil {
|
if auth == nil || auth.Form == nil {
|
||||||
return errors.New("form not found in response")
|
return errors.New("form not found in response")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,13 +82,11 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: nil,
|
Scopes: nil,
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://user.mypikpak.com/v1/auth/signin",
|
||||||
AuthURL: "https://user.mypikpak.com/v1/auth/signin",
|
TokenURL: "https://user.mypikpak.com/v1/auth/token",
|
||||||
TokenURL: "https://user.mypikpak.com/v1/auth/token",
|
AuthStyle: oauth2.AuthStyleInParams,
|
||||||
AuthStyle: oauth2.AuthStyleInParams,
|
|
||||||
},
|
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
"github.com/rclone/rclone/lib/random"
|
"github.com/rclone/rclone/lib/random"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -59,12 +58,10 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: nil,
|
Scopes: nil,
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://www.premiumize.me/authorize",
|
||||||
AuthURL: "https://www.premiumize.me/authorize",
|
TokenURL: "https://www.premiumize.me/token",
|
||||||
TokenURL: "https://www.premiumize.me/token",
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/dircache"
|
"github.com/rclone/rclone/lib/dircache"
|
||||||
"github.com/rclone/rclone/lib/encoder"
|
"github.com/rclone/rclone/lib/encoder"
|
||||||
"github.com/rclone/rclone/lib/oauthutil"
|
"github.com/rclone/rclone/lib/oauthutil"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -41,12 +40,10 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
putioConfig = &oauth2.Config{
|
putioConfig = &oauthutil.Config{
|
||||||
Scopes: []string{},
|
Scopes: []string{},
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://api.put.io/v2/oauth2/authenticate",
|
||||||
AuthURL: "https://api.put.io/v2/oauth2/authenticate",
|
TokenURL: "https://api.put.io/v2/oauth2/access_token",
|
||||||
TokenURL: "https://api.put.io/v2/oauth2/access_token",
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneObscuredClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneObscuredClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
"github.com/rclone/rclone/lib/random"
|
"github.com/rclone/rclone/lib/random"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -115,13 +114,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Generate a new oauth2 config which we will update when we know the TokenURL
|
// Generate a new oauth2 config which we will update when we know the TokenURL
|
||||||
func newOauthConfig(tokenURL string) *oauth2.Config {
|
func newOauthConfig(tokenURL string) *oauthutil.Config {
|
||||||
return &oauth2.Config{
|
return &oauthutil.Config{
|
||||||
Scopes: nil,
|
Scopes: nil,
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://secure.sharefile.com/oauth/authorize",
|
||||||
AuthURL: "https://secure.sharefile.com/oauth/authorize",
|
TokenURL: tokenURL,
|
||||||
TokenURL: tokenURL,
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectPublicSecureURL,
|
RedirectURL: oauthutil.RedirectPublicSecureURL,
|
||||||
@@ -136,7 +133,7 @@ func init() {
|
|||||||
NewFs: NewFs,
|
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, config fs.ConfigIn) (*fs.ConfigOut, error) {
|
||||||
oauthConfig := newOauthConfig("")
|
oauthConfig := newOauthConfig("")
|
||||||
checkAuth := func(oauthConfig *oauth2.Config, auth *oauthutil.AuthResult) error {
|
checkAuth := func(oauthConfig *oauthutil.Config, auth *oauthutil.AuthResult) error {
|
||||||
if auth == nil || auth.Form == nil {
|
if auth == nil || auth.Form == nil {
|
||||||
return errors.New("endpoint not found in response")
|
return errors.New("endpoint not found in response")
|
||||||
}
|
}
|
||||||
@@ -147,7 +144,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
endpoint := "https://" + subdomain + "." + apicp
|
endpoint := "https://" + subdomain + "." + apicp
|
||||||
m.Set("endpoint", endpoint)
|
m.Set("endpoint", endpoint)
|
||||||
oauthConfig.Endpoint.TokenURL = endpoint + tokenPath
|
oauthConfig.TokenURL = endpoint + tokenPath
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return oauthutil.ConfigOut("", &oauthutil.Options{
|
return oauthutil.ConfigOut("", &oauthutil.Options{
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/random"
|
"github.com/rclone/rclone/lib/random"
|
||||||
"github.com/rclone/rclone/lib/readers"
|
"github.com/rclone/rclone/lib/readers"
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// oAuth
|
// oAuth
|
||||||
@@ -47,11 +46,9 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Endpoint: oauth2.Endpoint{
|
AuthURL: "https://oauth.yandex.com/authorize", //same as https://oauth.yandex.ru/authorize
|
||||||
AuthURL: "https://oauth.yandex.com/authorize", //same as https://oauth.yandex.ru/authorize
|
TokenURL: "https://oauth.yandex.com/token", //same as https://oauth.yandex.ru/token
|
||||||
TokenURL: "https://oauth.yandex.com/token", //same as https://oauth.yandex.ru/token
|
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectURL,
|
RedirectURL: oauthutil.RedirectURL,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const (
|
|||||||
// Globals
|
// Globals
|
||||||
var (
|
var (
|
||||||
// Description of how to auth for this app
|
// Description of how to auth for this app
|
||||||
oauthConfig = &oauth2.Config{
|
oauthConfig = &oauthutil.Config{
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"aaaserver.profile.read",
|
"aaaserver.profile.read",
|
||||||
"WorkDrive.team.READ",
|
"WorkDrive.team.READ",
|
||||||
@@ -55,11 +55,10 @@ var (
|
|||||||
"WorkDrive.files.ALL",
|
"WorkDrive.files.ALL",
|
||||||
"ZohoFiles.files.ALL",
|
"ZohoFiles.files.ALL",
|
||||||
},
|
},
|
||||||
Endpoint: oauth2.Endpoint{
|
|
||||||
AuthURL: "https://accounts.zoho.eu/oauth/v2/auth",
|
AuthURL: "https://accounts.zoho.eu/oauth/v2/auth",
|
||||||
TokenURL: "https://accounts.zoho.eu/oauth/v2/token",
|
TokenURL: "https://accounts.zoho.eu/oauth/v2/token",
|
||||||
AuthStyle: oauth2.AuthStyleInParams,
|
AuthStyle: oauth2.AuthStyleInParams,
|
||||||
},
|
|
||||||
ClientID: rcloneClientID,
|
ClientID: rcloneClientID,
|
||||||
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
||||||
RedirectURL: oauthutil.RedirectLocalhostURL,
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
||||||
@@ -276,8 +275,8 @@ func setupRegion(m configmap.Mapper) error {
|
|||||||
downloadURL = fmt.Sprintf("https://download.zoho.%s/v1/workdrive", region)
|
downloadURL = fmt.Sprintf("https://download.zoho.%s/v1/workdrive", region)
|
||||||
uploadURL = fmt.Sprintf("https://upload.zoho.%s/workdrive-api/v1", region)
|
uploadURL = fmt.Sprintf("https://upload.zoho.%s/workdrive-api/v1", region)
|
||||||
accountsURL = fmt.Sprintf("https://accounts.zoho.%s", region)
|
accountsURL = fmt.Sprintf("https://accounts.zoho.%s", region)
|
||||||
oauthConfig.Endpoint.AuthURL = fmt.Sprintf("https://accounts.zoho.%s/oauth/v2/auth", region)
|
oauthConfig.AuthURL = fmt.Sprintf("https://accounts.zoho.%s/oauth/v2/auth", region)
|
||||||
oauthConfig.Endpoint.TokenURL = fmt.Sprintf("https://accounts.zoho.%s/oauth/v2/token", region)
|
oauthConfig.TokenURL = fmt.Sprintf("https://accounts.zoho.%s/oauth/v2/token", region)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,27 @@ You may try to [verify you account](https://docs.microsoft.com/en-us/azure/activ
|
|||||||
|
|
||||||
Note: If you have a special region, you may need a different host in step 4 and 5. Here are [some hints](https://github.com/rclone/rclone/blob/bc23bf11db1c78c6ebbf8ea538fbebf7058b4176/backend/onedrive/onedrive.go#L86).
|
Note: If you have a special region, you may need a different host in step 4 and 5. Here are [some hints](https://github.com/rclone/rclone/blob/bc23bf11db1c78c6ebbf8ea538fbebf7058b4176/backend/onedrive/onedrive.go#L86).
|
||||||
|
|
||||||
|
### Using OAuth Client Credential flow
|
||||||
|
|
||||||
|
OAuth Client Credential flow will allow rclone to use permissions
|
||||||
|
directly associated with the Azure AD Enterprise application, rather
|
||||||
|
that adopting the context of an Azure AD user account.
|
||||||
|
|
||||||
|
This flow can be enabled by following the steps below:
|
||||||
|
|
||||||
|
1. Create the Enterprise App registration in the Azure AD portal and obtain a Client ID and Client Secret as described above.
|
||||||
|
2. Ensure that the application has the appropriate permissions and they are assigned as *Application Permissions*
|
||||||
|
3. Configure the remote, ensuring that *Client ID* and *Client Secret* are entered correctly.
|
||||||
|
4. In the *Advanced Config* section, enter `true` for `client_credentials` and in the `tenant` section enter the tenant ID.
|
||||||
|
|
||||||
|
When it comes to choosing the type of the connection work with the
|
||||||
|
client credentials flow. In particular the "onedrive" option does not
|
||||||
|
work. You can use the "sharepoint" option or if that does not find the
|
||||||
|
correct drive ID type it in manually with the "driveid" option.
|
||||||
|
|
||||||
|
**NOTE** Assigning permissions directly to the application means that
|
||||||
|
anyone with the *Client ID* and *Client Secret* can access your
|
||||||
|
OneDrive files. Take care to safeguard these credentials.
|
||||||
|
|
||||||
### Modification times and hashes
|
### Modification times and hashes
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ const (
|
|||||||
// ConfigTokenURL is the config key used to store the token server endpoint
|
// ConfigTokenURL is the config key used to store the token server endpoint
|
||||||
ConfigTokenURL = "token_url"
|
ConfigTokenURL = "token_url"
|
||||||
|
|
||||||
|
// ConfigClientCredentials - use OAUTH2 client credentials
|
||||||
|
ConfigClientCredentials = "client_credentials"
|
||||||
|
|
||||||
// ConfigEncoding is the config key to change the encoding for a backend
|
// ConfigEncoding is the config key to change the encoding for a backend
|
||||||
ConfigEncoding = "encoding"
|
ConfigEncoding = "encoding"
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -23,6 +24,7 @@ import (
|
|||||||
"github.com/rclone/rclone/lib/random"
|
"github.com/rclone/rclone/lib/random"
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -85,6 +87,49 @@ All done. Please go back to rclone.
|
|||||||
// should work for most uses, but may be overridden.
|
// should work for most uses, but may be overridden.
|
||||||
var OpenURL = open.Start
|
var OpenURL = open.Start
|
||||||
|
|
||||||
|
// Config - structure that we will use to store the OAuth configuration
|
||||||
|
// settings. This is based on the union of the configuration structures for the two
|
||||||
|
// OAuth modules that we are using (oauth2 and oauth2.clientcrentials), along with a
|
||||||
|
// flag indicating if we are going to use the client credential flow
|
||||||
|
type Config struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
TokenURL string
|
||||||
|
AuthURL string
|
||||||
|
Scopes []string
|
||||||
|
EndpointParams url.Values
|
||||||
|
RedirectURL string
|
||||||
|
ClientCredentialFlow bool
|
||||||
|
AuthStyle oauth2.AuthStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeOauth2Config makes an oauth2.Config from our config
|
||||||
|
func (conf *Config) MakeOauth2Config() *oauth2.Config {
|
||||||
|
return &oauth2.Config{
|
||||||
|
ClientID: conf.ClientID,
|
||||||
|
ClientSecret: conf.ClientSecret,
|
||||||
|
RedirectURL: RedirectLocalhostURL,
|
||||||
|
Scopes: conf.Scopes,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: conf.AuthURL,
|
||||||
|
TokenURL: conf.TokenURL,
|
||||||
|
AuthStyle: conf.AuthStyle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeClientCredentialsConfig makes a clientcredentials.Config from our config
|
||||||
|
func (conf *Config) MakeClientCredentialsConfig() *clientcredentials.Config {
|
||||||
|
return &clientcredentials.Config{
|
||||||
|
ClientID: conf.ClientID,
|
||||||
|
ClientSecret: conf.ClientSecret,
|
||||||
|
Scopes: conf.Scopes,
|
||||||
|
TokenURL: conf.TokenURL,
|
||||||
|
AuthStyle: conf.AuthStyle,
|
||||||
|
// EndpointParams url.Values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SharedOptions are shared between backends the utilize an OAuth flow
|
// SharedOptions are shared between backends the utilize an OAuth flow
|
||||||
var SharedOptions = []fs.Option{{
|
var SharedOptions = []fs.Option{{
|
||||||
Name: config.ConfigClientID,
|
Name: config.ConfigClientID,
|
||||||
@@ -107,6 +152,11 @@ var SharedOptions = []fs.Option{{
|
|||||||
Name: config.ConfigTokenURL,
|
Name: config.ConfigTokenURL,
|
||||||
Help: "Token server url.\n\nLeave blank to use the provider defaults.",
|
Help: "Token server url.\n\nLeave blank to use the provider defaults.",
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: config.ConfigClientCredentials,
|
||||||
|
Default: false,
|
||||||
|
Help: "Use client credentials OAuth flow.\n\nThis will use the OAUTH2 client Credentials Flow as described in RFC 6749.",
|
||||||
|
Advanced: true,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// oldToken contains an end-user's tokens.
|
// oldToken contains an end-user's tokens.
|
||||||
@@ -178,7 +228,7 @@ type TokenSource struct {
|
|||||||
m configmap.Mapper
|
m configmap.Mapper
|
||||||
tokenSource oauth2.TokenSource
|
tokenSource oauth2.TokenSource
|
||||||
token *oauth2.Token
|
token *oauth2.Token
|
||||||
config *oauth2.Config
|
config *Config
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
expiryTimer *time.Timer // signals whenever the token expires
|
expiryTimer *time.Timer // signals whenever the token expires
|
||||||
}
|
}
|
||||||
@@ -264,6 +314,11 @@ func (ts *TokenSource) Token() (*oauth2.Token, error) {
|
|||||||
)
|
)
|
||||||
const maxTries = 5
|
const maxTries = 5
|
||||||
|
|
||||||
|
// If we have a cached valid token, use that
|
||||||
|
if ts.token.Valid() {
|
||||||
|
return ts.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Try getting the token a few times
|
// Try getting the token a few times
|
||||||
for i := 1; i <= maxTries; i++ {
|
for i := 1; i <= maxTries; i++ {
|
||||||
// Try reading the token from the config file in case it has
|
// Try reading the token from the config file in case it has
|
||||||
@@ -271,7 +326,7 @@ func (ts *TokenSource) Token() (*oauth2.Token, error) {
|
|||||||
if !ts.token.Valid() {
|
if !ts.token.Valid() {
|
||||||
if ts.reReadToken() {
|
if ts.reReadToken() {
|
||||||
changed = true
|
changed = true
|
||||||
} else if ts.token.RefreshToken == "" {
|
} else if !ts.config.ClientCredentialFlow && ts.token.RefreshToken == "" {
|
||||||
return nil, fserrors.FatalError(
|
return nil, fserrors.FatalError(
|
||||||
fmt.Errorf("token expired and there's no refresh token - manually refresh with \"rclone config reconnect %s:\"", ts.name),
|
fmt.Errorf("token expired and there's no refresh token - manually refresh with \"rclone config reconnect %s:\"", ts.name),
|
||||||
)
|
)
|
||||||
@@ -280,7 +335,11 @@ func (ts *TokenSource) Token() (*oauth2.Token, error) {
|
|||||||
|
|
||||||
// Make a new token source if required
|
// Make a new token source if required
|
||||||
if ts.tokenSource == nil {
|
if ts.tokenSource == nil {
|
||||||
ts.tokenSource = ts.config.TokenSource(ts.ctx, ts.token)
|
if ts.config.ClientCredentialFlow {
|
||||||
|
ts.tokenSource = ts.config.MakeClientCredentialsConfig().TokenSource(ts.ctx)
|
||||||
|
} else {
|
||||||
|
ts.tokenSource = ts.config.MakeOauth2Config().TokenSource(ts.ctx, ts.token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err = ts.tokenSource.Token()
|
token, err = ts.tokenSource.Token()
|
||||||
@@ -297,7 +356,7 @@ func (ts *TokenSource) Token() (*oauth2.Token, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't fetch token: %w", err)
|
return nil, fmt.Errorf("couldn't fetch token: %w", err)
|
||||||
}
|
}
|
||||||
changed = changed || token.AccessToken != ts.token.AccessToken || token.RefreshToken != ts.token.RefreshToken || token.Expiry != ts.token.Expiry
|
changed = changed || ts.token == nil || token.AccessToken != ts.token.AccessToken || token.RefreshToken != ts.token.RefreshToken || token.Expiry != ts.token.Expiry
|
||||||
ts.token = token
|
ts.token = token
|
||||||
if changed {
|
if changed {
|
||||||
// Bump on the expiry timer if it is set
|
// Bump on the expiry timer if it is set
|
||||||
@@ -370,12 +429,12 @@ func Context(ctx context.Context, client *http.Client) context.Context {
|
|||||||
return context.WithValue(ctx, oauth2.HTTPClient, client)
|
return context.WithValue(ctx, oauth2.HTTPClient, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideCredentials sets the ClientID and ClientSecret from the
|
// OverrideCredentials sets the ClientID and ClientSecret from the
|
||||||
// config file if they are not blank.
|
// config file if they are not blank.
|
||||||
// If any value is overridden, true is returned.
|
// If any value is overridden, true is returned.
|
||||||
// the origConfig is copied
|
// the origConfig is copied
|
||||||
func overrideCredentials(name string, m configmap.Mapper, origConfig *oauth2.Config) (newConfig *oauth2.Config, changed bool) {
|
func OverrideCredentials(name string, m configmap.Mapper, origConfig *Config) (newConfig *Config, changed bool) {
|
||||||
newConfig = new(oauth2.Config)
|
newConfig = new(Config)
|
||||||
*newConfig = *origConfig
|
*newConfig = *origConfig
|
||||||
changed = false
|
changed = false
|
||||||
ClientID, ok := m.Get(config.ConfigClientID)
|
ClientID, ok := m.Get(config.ConfigClientID)
|
||||||
@@ -393,12 +452,22 @@ func overrideCredentials(name string, m configmap.Mapper, origConfig *oauth2.Con
|
|||||||
}
|
}
|
||||||
AuthURL, ok := m.Get(config.ConfigAuthURL)
|
AuthURL, ok := m.Get(config.ConfigAuthURL)
|
||||||
if ok && AuthURL != "" {
|
if ok && AuthURL != "" {
|
||||||
newConfig.Endpoint.AuthURL = AuthURL
|
newConfig.AuthURL = AuthURL
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
TokenURL, ok := m.Get(config.ConfigTokenURL)
|
TokenURL, ok := m.Get(config.ConfigTokenURL)
|
||||||
if ok && TokenURL != "" {
|
if ok && TokenURL != "" {
|
||||||
newConfig.Endpoint.TokenURL = TokenURL
|
newConfig.TokenURL = TokenURL
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
ClientCredentialStr, ok := m.Get(config.ConfigClientCredentials)
|
||||||
|
if ok && ClientCredentialStr != "" {
|
||||||
|
ClientCredential, err := strconv.ParseBool(ClientCredentialStr)
|
||||||
|
if err != nil {
|
||||||
|
fs.Errorf(nil, "Invalid setting for %q: %v", config.ConfigClientCredentials, err)
|
||||||
|
} else {
|
||||||
|
newConfig.ClientCredentialFlow = ClientCredential
|
||||||
|
}
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
return newConfig, changed
|
return newConfig, changed
|
||||||
@@ -408,8 +477,8 @@ func overrideCredentials(name string, m configmap.Mapper, origConfig *oauth2.Con
|
|||||||
// configures a Client with it. It returns the client and a
|
// configures a Client with it. It returns the client and a
|
||||||
// TokenSource which Invalidate may need to be called on. It uses the
|
// TokenSource which Invalidate may need to be called on. It uses the
|
||||||
// httpClient passed in as the base client.
|
// httpClient passed in as the base client.
|
||||||
func NewClientWithBaseClient(ctx context.Context, name string, m configmap.Mapper, config *oauth2.Config, baseClient *http.Client) (*http.Client, *TokenSource, error) {
|
func NewClientWithBaseClient(ctx context.Context, name string, m configmap.Mapper, config *Config, baseClient *http.Client) (*http.Client, *TokenSource, error) {
|
||||||
config, _ = overrideCredentials(name, m, config)
|
config, _ = OverrideCredentials(name, m, config)
|
||||||
token, err := GetToken(name, m)
|
token, err := GetToken(name, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -428,12 +497,39 @@ func NewClientWithBaseClient(ctx context.Context, name string, m configmap.Mappe
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
return oauth2.NewClient(ctx, ts), ts, nil
|
return oauth2.NewClient(ctx, ts), ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientCredentialsClient creates a new OAuth module using the
|
||||||
|
// ClientCredential flow
|
||||||
|
func NewClientCredentialsClient(ctx context.Context, name string, m configmap.Mapper, oauthConfig *Config, baseClient *http.Client) (*http.Client, *TokenSource, error) {
|
||||||
|
oauthConfig, _ = OverrideCredentials(name, m, oauthConfig)
|
||||||
|
token, _ := GetToken(name, m)
|
||||||
|
// If the token doesn't exist then we will fetch one in the next step as we don't need a refresh token
|
||||||
|
|
||||||
|
// Set our own http client in the context
|
||||||
|
ctx = Context(ctx, baseClient)
|
||||||
|
|
||||||
|
// Wrap the TokenSource in our TokenSource which saves changed
|
||||||
|
// tokens in the config file
|
||||||
|
ts := &TokenSource{
|
||||||
|
name: name,
|
||||||
|
m: m,
|
||||||
|
token: token,
|
||||||
|
config: oauthConfig,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
return oauth2.NewClient(ctx, ts), ts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient gets a token from the config file and configures a Client
|
// 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
|
// with it. It returns the client and a TokenSource which Invalidate
|
||||||
func NewClient(ctx context.Context, name string, m configmap.Mapper, oauthConfig *oauth2.Config) (*http.Client, *TokenSource, error) {
|
// may need to be called on
|
||||||
|
func NewClient(ctx context.Context, name string, m configmap.Mapper, oauthConfig *Config) (*http.Client, *TokenSource, error) {
|
||||||
|
// Check whether we are using the client credentials flow
|
||||||
|
if oauthConfig.ClientCredentialFlow {
|
||||||
|
|
||||||
|
return NewClientCredentialsClient(ctx, name, m, oauthConfig, fshttp.NewClient(ctx))
|
||||||
|
}
|
||||||
return NewClientWithBaseClient(ctx, name, m, oauthConfig, fshttp.NewClient(ctx))
|
return NewClientWithBaseClient(ctx, name, m, oauthConfig, fshttp.NewClient(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,11 +556,11 @@ func (ar *AuthResult) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckAuthFn is called when a good Auth has been received
|
// CheckAuthFn is called when a good Auth has been received
|
||||||
type CheckAuthFn func(*oauth2.Config, *AuthResult) error
|
type CheckAuthFn func(*Config, *AuthResult) error
|
||||||
|
|
||||||
// Options for the oauth config
|
// Options for the oauth config
|
||||||
type Options struct {
|
type Options struct {
|
||||||
OAuth2Config *oauth2.Config // Basic config for oauth2
|
OAuth2Config *Config // Basic config for oauth2
|
||||||
NoOffline bool // If set then "access_type=offline" parameter is not passed
|
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
|
CheckAuth CheckAuthFn // When the AuthResult is known the checkAuth function is called if set
|
||||||
OAuth2Opts []oauth2.AuthCodeOption // extra oauth2 options
|
OAuth2Opts []oauth2.AuthCodeOption // extra oauth2 options
|
||||||
@@ -532,6 +628,15 @@ func ConfigOAuth(ctx context.Context, name string, m configmap.Mapper, ri *fs.Re
|
|||||||
if in.Result == "false" {
|
if in.Result == "false" {
|
||||||
return fs.ConfigGoto(newState("*oauth-done"))
|
return fs.ConfigGoto(newState("*oauth-done"))
|
||||||
}
|
}
|
||||||
|
opt, err := getOAuth()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oauthConfig, _ := OverrideCredentials(name, m, opt.OAuth2Config)
|
||||||
|
if oauthConfig.ClientCredentialFlow {
|
||||||
|
// If using client credential flow, skip straight to getting the token since we don't need a browser
|
||||||
|
return fs.ConfigGoto(newState("*oauth-do"))
|
||||||
|
}
|
||||||
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")
|
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")
|
||||||
case "*oauth-islocal":
|
case "*oauth-islocal":
|
||||||
if in.Result == "true" {
|
if in.Result == "true" {
|
||||||
@@ -626,20 +731,27 @@ version recommended):
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
oauthConfig, changed := overrideCredentials(name, m, opt.OAuth2Config)
|
oauthConfig, changed := OverrideCredentials(name, m, opt.OAuth2Config)
|
||||||
if changed {
|
if changed {
|
||||||
fs.Logf(nil, "Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
|
fs.Logf(nil, "Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
|
||||||
}
|
}
|
||||||
if code == "" {
|
if oauthConfig.ClientCredentialFlow {
|
||||||
oauthConfig = fixRedirect(oauthConfig)
|
err = clientCredentialsFlowGetToken(ctx, name, m, oauthConfig, opt)
|
||||||
code, err = configSetup(ctx, ri.Name, name, m, oauthConfig, opt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("config failed to refresh token: %w", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if code == "" {
|
||||||
|
oauthConfig = fixRedirect(oauthConfig)
|
||||||
|
code, err = configSetup(ctx, ri.Name, name, m, oauthConfig, opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("config failed to refresh token: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = configExchange(ctx, name, m, oauthConfig, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
err = configExchange(ctx, name, m, oauthConfig, code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return fs.ConfigGoto(newState("*oauth-done"))
|
return fs.ConfigGoto(newState("*oauth-done"))
|
||||||
case "*oauth-done":
|
case "*oauth-done":
|
||||||
@@ -656,13 +768,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return true if can run without a webserver and just entering a code
|
// Return true if can run without a webserver and just entering a code
|
||||||
func noWebserverNeeded(oauthConfig *oauth2.Config) bool {
|
func noWebserverNeeded(oauthConfig *Config) bool {
|
||||||
return oauthConfig.RedirectURL == TitleBarRedirectURL
|
return oauthConfig.RedirectURL == TitleBarRedirectURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the URL we need to send the user to
|
// 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) {
|
func getAuthURL(name string, m configmap.Mapper, oauthConfig *Config, opt *Options) (authURL string, state string, err error) {
|
||||||
oauthConfig, _ = overrideCredentials(name, m, oauthConfig)
|
oauthConfig, _ = OverrideCredentials(name, m, oauthConfig)
|
||||||
|
|
||||||
// Make random state
|
// Make random state
|
||||||
state, err = random.Password(128)
|
state, err = random.Password(128)
|
||||||
@@ -670,18 +782,21 @@ func getAuthURL(name string, m configmap.Mapper, oauthConfig *oauth2.Config, opt
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the configuration required for the OAuth flow
|
||||||
|
oauth2Conf := oauthConfig.MakeOauth2Config()
|
||||||
|
|
||||||
// Generate oauth URL
|
// Generate oauth URL
|
||||||
opts := opt.OAuth2Opts
|
opts := opt.OAuth2Opts
|
||||||
if !opt.NoOffline {
|
if !opt.NoOffline {
|
||||||
opts = append(opts, oauth2.AccessTypeOffline)
|
opts = append(opts, oauth2.AccessTypeOffline)
|
||||||
}
|
}
|
||||||
authURL = oauthConfig.AuthCodeURL(state, opts...)
|
authURL = oauth2Conf.AuthCodeURL(state, opts...)
|
||||||
return authURL, state, nil
|
return authURL, state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If TitleBarRedirect is set but we are doing a real oauth, then
|
// If TitleBarRedirect is set but we are doing a real oauth, then
|
||||||
// override our redirect URL
|
// override our redirect URL
|
||||||
func fixRedirect(oauthConfig *oauth2.Config) *oauth2.Config {
|
func fixRedirect(oauthConfig *Config) *Config {
|
||||||
switch oauthConfig.RedirectURL {
|
switch oauthConfig.RedirectURL {
|
||||||
case TitleBarRedirectURL:
|
case TitleBarRedirectURL:
|
||||||
// copy the config and set to use the internal webserver
|
// copy the config and set to use the internal webserver
|
||||||
@@ -692,12 +807,33 @@ func fixRedirect(oauthConfig *oauth2.Config) *oauth2.Config {
|
|||||||
return oauthConfig
|
return oauthConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// configSetup does the initial creation of the token for the client credentials flow
|
||||||
|
//
|
||||||
|
// If opt is nil it will use the default Options.
|
||||||
|
func clientCredentialsFlowGetToken(ctx context.Context, name string, m configmap.Mapper, oauthConfig *Config, opt *Options) error {
|
||||||
|
if opt == nil {
|
||||||
|
opt = &Options{}
|
||||||
|
}
|
||||||
|
_ = opt // not currently using the Options
|
||||||
|
fs.Debugf(nil, "Getting token for client credentials flow")
|
||||||
|
_, tokenSource, err := NewClientCredentialsClient(ctx, name, m, oauthConfig, fshttp.NewClient(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client credentials flow: failed to make client: %w", err)
|
||||||
|
}
|
||||||
|
// Get the token and save it in the config file
|
||||||
|
_, err = tokenSource.Token()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client credentials flow: failed to get token: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// configSetup does the initial creation of the token
|
// configSetup does the initial creation of the token
|
||||||
//
|
//
|
||||||
// If opt is nil it will use the default Options.
|
// If opt is nil it will use the default Options.
|
||||||
//
|
//
|
||||||
// It will run an internal webserver to receive the results
|
// 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) {
|
func configSetup(ctx context.Context, id, name string, m configmap.Mapper, oauthConfig *Config, opt *Options) (string, error) {
|
||||||
if opt == nil {
|
if opt == nil {
|
||||||
opt = &Options{}
|
opt = &Options{}
|
||||||
}
|
}
|
||||||
@@ -749,9 +885,13 @@ func configSetup(ctx context.Context, id, name string, m configmap.Mapper, oauth
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exchange the code for a token
|
// Exchange the code for a token
|
||||||
func configExchange(ctx context.Context, name string, m configmap.Mapper, oauthConfig *oauth2.Config, code string) error {
|
func configExchange(ctx context.Context, name string, m configmap.Mapper, oauthConfig *Config, code string) error {
|
||||||
ctx = Context(ctx, fshttp.NewClient(ctx))
|
ctx = Context(ctx, fshttp.NewClient(ctx))
|
||||||
token, err := oauthConfig.Exchange(ctx, code)
|
|
||||||
|
// Create the configuration required for the OAuth flow
|
||||||
|
oauth2Conf := oauthConfig.MakeOauth2Config()
|
||||||
|
|
||||||
|
token, err := oauth2Conf.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get token: %w", err)
|
return fmt.Errorf("failed to get token: %w", err)
|
||||||
}
|
}
|
||||||
@@ -813,10 +953,17 @@ func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) {
|
|||||||
// get code, error if empty
|
// get code, error if empty
|
||||||
code := req.Form.Get("code")
|
code := req.Form.Get("code")
|
||||||
if code == "" {
|
if code == "" {
|
||||||
reply(http.StatusBadRequest, &AuthResult{
|
err := &AuthResult{
|
||||||
Name: "Auth Error",
|
Name: "Auth Error",
|
||||||
Description: "No code returned by remote server",
|
Description: "No code returned by remote server",
|
||||||
})
|
}
|
||||||
|
if errorCode := req.Form.Get("error"); errorCode != "" {
|
||||||
|
err.Description += ": " + errorCode
|
||||||
|
}
|
||||||
|
if errorMessage := req.Form.Get("error_description"); errorMessage != "" {
|
||||||
|
err.Description += ": " + errorMessage
|
||||||
|
}
|
||||||
|
reply(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user