mirror of
https://github.com/rclone/rclone.git
synced 2025-12-06 00:03:32 +00:00
Compare commits
1 Commits
test
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9adc7007e |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -283,7 +283,7 @@ jobs:
|
|||||||
run: govulncheck ./...
|
run: govulncheck ./...
|
||||||
|
|
||||||
- name: Check Markdown format
|
- name: Check Markdown format
|
||||||
uses: DavidAnson/markdownlint-cli2-action@v20
|
uses: DavidAnson/markdownlint-cli2-action@v21
|
||||||
with:
|
with:
|
||||||
globs: |
|
globs: |
|
||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
||||||
|
|||||||
@@ -87,11 +87,13 @@ func init() {
|
|||||||
Description: "Box",
|
Description: "Box",
|
||||||
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) {
|
||||||
|
jsonFile, ok := m.Get("box_config_file")
|
||||||
|
boxSubType, boxSubTypeOk := m.Get("box_sub_type")
|
||||||
boxAccessToken, boxAccessTokenOk := m.Get("access_token")
|
boxAccessToken, boxAccessTokenOk := m.Get("access_token")
|
||||||
var err error
|
var err error
|
||||||
// If using box config.json, use JWT auth
|
// If using box config.json, use JWT auth
|
||||||
if usesJWTAuth(m) {
|
if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" {
|
||||||
err = refreshJWTToken(ctx, name, m)
|
err = refreshJWTToken(ctx, jsonFile, boxSubType, name, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to configure token with jwt authentication: %w", err)
|
return nil, fmt.Errorf("failed to configure token with jwt authentication: %w", err)
|
||||||
}
|
}
|
||||||
@@ -112,11 +114,6 @@ func init() {
|
|||||||
}, {
|
}, {
|
||||||
Name: "box_config_file",
|
Name: "box_config_file",
|
||||||
Help: "Box App config.json location\n\nLeave blank normally." + env.ShellExpandHelp,
|
Help: "Box App config.json location\n\nLeave blank normally." + env.ShellExpandHelp,
|
||||||
}, {
|
|
||||||
Name: "config_credentials",
|
|
||||||
Help: "Box App config.json contents.\n\nLeave blank normally.",
|
|
||||||
Hide: fs.OptionHideBoth,
|
|
||||||
Sensitive: true,
|
|
||||||
}, {
|
}, {
|
||||||
Name: "access_token",
|
Name: "access_token",
|
||||||
Help: "Box App Primary Access Token\n\nLeave blank normally.",
|
Help: "Box App Primary Access Token\n\nLeave blank normally.",
|
||||||
@@ -187,17 +184,9 @@ See: https://developer.box.com/guides/authentication/jwt/as-user/
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func usesJWTAuth(m configmap.Mapper) bool {
|
func refreshJWTToken(ctx context.Context, jsonFile string, boxSubType string, name string, m configmap.Mapper) error {
|
||||||
jsonFile, okFile := m.Get("box_config_file")
|
jsonFile = env.ShellExpand(jsonFile)
|
||||||
jsonFileCredentials, okCredentials := m.Get("config_credentials")
|
boxConfig, err := getBoxConfig(jsonFile)
|
||||||
boxSubType, boxSubTypeOk := m.Get("box_sub_type")
|
|
||||||
return (okFile || okCredentials) && boxSubTypeOk && (jsonFile != "" || jsonFileCredentials != "") && boxSubType != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshJWTToken(ctx context.Context, name string, m configmap.Mapper) error {
|
|
||||||
boxSubType, _ := m.Get("box_sub_type")
|
|
||||||
|
|
||||||
boxConfig, err := getBoxConfig(m)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get box config: %w", err)
|
return fmt.Errorf("get box config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -216,19 +205,12 @@ func refreshJWTToken(ctx context.Context, name string, m configmap.Mapper) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBoxConfig(m configmap.Mapper) (boxConfig *api.ConfigJSON, err error) {
|
func getBoxConfig(configFile string) (boxConfig *api.ConfigJSON, err error) {
|
||||||
configFileCredentials, _ := m.Get("config_credentials")
|
file, err := os.ReadFile(configFile)
|
||||||
configFileBytes := []byte(configFileCredentials)
|
|
||||||
|
|
||||||
if configFileCredentials == "" {
|
|
||||||
configFile, _ := m.Get("box_config_file")
|
|
||||||
configFileBytes, err = os.ReadFile(configFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("box: failed to read Box config: %w", err)
|
return nil, fmt.Errorf("box: failed to read Box config: %w", err)
|
||||||
}
|
}
|
||||||
}
|
err = json.Unmarshal(file, &boxConfig)
|
||||||
|
|
||||||
err = json.Unmarshal(configFileBytes, &boxConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("box: failed to parse Box config: %w", err)
|
return nil, fmt.Errorf("box: failed to parse Box config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -503,12 +485,15 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
|||||||
f.srv.SetHeader("as-user", f.opt.Impersonate)
|
f.srv.SetHeader("as-user", f.opt.Impersonate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonFile, ok := m.Get("box_config_file")
|
||||||
|
boxSubType, boxSubTypeOk := m.Get("box_sub_type")
|
||||||
|
|
||||||
if ts != nil {
|
if ts != nil {
|
||||||
// If using box config.json and JWT, renewing should just refresh the token and
|
// If using box config.json and JWT, renewing should just refresh the token and
|
||||||
// should do so whether there are uploads pending or not.
|
// should do so whether there are uploads pending or not.
|
||||||
if usesJWTAuth(m) {
|
if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" {
|
||||||
f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
|
f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
|
||||||
err := refreshJWTToken(ctx, name, m)
|
err := refreshJWTToken(ctx, jsonFile, boxSubType, name, m)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
f.tokenRenewer.Start()
|
f.tokenRenewer.Start()
|
||||||
|
|||||||
@@ -1648,14 +1648,11 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
|||||||
newRoot, leaf := path.Split(oldRoot)
|
newRoot, leaf := path.Split(oldRoot)
|
||||||
f.setRoot(newRoot)
|
f.setRoot(newRoot)
|
||||||
_, err := f.NewObject(ctx, leaf)
|
_, err := f.NewObject(ctx, leaf)
|
||||||
if errors.Is(err, fs.ErrorObjectNotFound) {
|
if err != nil {
|
||||||
// File doesn't exist or is a directory so return old f
|
// File doesn't exist or is a directory so return old f
|
||||||
f.setRoot(oldRoot)
|
f.setRoot(oldRoot)
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// return an error with an fs which points to the parent
|
// return an error with an fs which points to the parent
|
||||||
return f, fs.ErrorIsFile
|
return f, fs.ErrorIsFile
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1045,5 +1045,3 @@ put them back in again. -->
|
|||||||
- n4n5 <its.just.n4n5@gmail.com>
|
- n4n5 <its.just.n4n5@gmail.com>
|
||||||
- aliaj1 <ali19961@gmail.com>
|
- aliaj1 <ali19961@gmail.com>
|
||||||
- Sean Turner <30396892+seanturner026@users.noreply.github.com>
|
- Sean Turner <30396892+seanturner026@users.noreply.github.com>
|
||||||
- jijamik <30904953+jijamik@users.noreply.github.com>
|
|
||||||
- Dominik Sander <git@dsander.de>
|
|
||||||
|
|||||||
@@ -221,18 +221,6 @@ client credentials flow. In particular the "onedrive" option does not
|
|||||||
work. You can use the "sharepoint" option or if that does not find the
|
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.
|
correct drive ID type it in manually with the "driveid" option.
|
||||||
|
|
||||||
To back up any user's data using this flow, grant your Azure AD
|
|
||||||
application the necessary Microsoft Graph *Application permissions*
|
|
||||||
(such as `Files.Read.All`, `Sites.Read.All` and/or `Sites.Selected`).
|
|
||||||
With these permissions, rclone can access drives across the tenant,
|
|
||||||
but it needs to know *which user or drive* you want. Supply a specific
|
|
||||||
`drive_id` corresponding to that user's OneDrive, or a SharePoint site
|
|
||||||
ID for SharePoint libraries. You can obtain a user's drive ID using
|
|
||||||
Microsoft Graph (e.g. `/users/{userUPN}/drive`) and then configure it
|
|
||||||
in rclone. Once the correct drive ID is provided, rclone will back up
|
|
||||||
that user's data using the app-only token without requiring their
|
|
||||||
credentials.
|
|
||||||
|
|
||||||
**NOTE** Assigning permissions directly to the application means that
|
**NOTE** Assigning permissions directly to the application means that
|
||||||
anyone with the *Client ID* and *Client Secret* can access your
|
anyone with the *Client ID* and *Client Secret* can access your
|
||||||
OneDrive files. Take care to safeguard these credentials.
|
OneDrive files. Take care to safeguard these credentials.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Unlocks the config file if it is locked.
|
|||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
- 'configPassword' - password to unlock the config file
|
- 'config_password' - password to unlock the config file
|
||||||
|
|
||||||
A good idea is to disable AskPassword before making this call
|
A good idea is to disable AskPassword before making this call
|
||||||
`,
|
`,
|
||||||
@@ -30,14 +30,10 @@ A good idea is to disable AskPassword before making this call
|
|||||||
// Unlock the config file
|
// Unlock the config file
|
||||||
// A good idea is to disable AskPassword before making this call
|
// A good idea is to disable AskPassword before making this call
|
||||||
func rcConfigPassword(ctx context.Context, in rc.Params) (out rc.Params, err error) {
|
func rcConfigPassword(ctx context.Context, in rc.Params) (out rc.Params, err error) {
|
||||||
configPass, err := in.GetString("configPassword")
|
configPass, err := in.GetString("config_password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var err2 error
|
|
||||||
configPass, err2 = in.GetString("config_password") // backwards compat
|
|
||||||
if err2 != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if SetConfigPassword(configPass) != nil {
|
if SetConfigPassword(configPass) != nil {
|
||||||
return nil, errors.New("failed to set config password")
|
return nil, errors.New("failed to set config password")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,26 +215,13 @@ func TestRcPaths(t *testing.T) {
|
|||||||
func TestRcConfigUnlock(t *testing.T) {
|
func TestRcConfigUnlock(t *testing.T) {
|
||||||
call := rc.Calls.Get("config/unlock")
|
call := rc.Calls.Get("config/unlock")
|
||||||
assert.NotNil(t, call)
|
assert.NotNil(t, call)
|
||||||
|
|
||||||
in := rc.Params{
|
in := rc.Params{
|
||||||
"configPassword": "test",
|
"config_password": "test",
|
||||||
}
|
}
|
||||||
out, err := call.Fn(context.Background(), in)
|
out, err := call.Fn(context.Background(), in)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
assert.Nil(t, out)
|
assert.Nil(t, out)
|
||||||
|
|
||||||
in = rc.Params{
|
|
||||||
"config_password": "test",
|
|
||||||
}
|
|
||||||
out, err = call.Fn(context.Background(), in)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Nil(t, out)
|
|
||||||
|
|
||||||
in = rc.Params{
|
|
||||||
"bad_config_password": "test",
|
|
||||||
}
|
|
||||||
out, err = call.Fn(context.Background(), in)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.ErrorContains(t, err, `Didn't find key "configPassword" in input`)
|
|
||||||
assert.Nil(t, out)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -425,8 +425,8 @@ Results:
|
|||||||
|
|
||||||
- executeId - string id of rclone executing (change after restart)
|
- executeId - string id of rclone executing (change after restart)
|
||||||
- jobids - array of integer job ids (starting at 1 on each restart)
|
- jobids - array of integer job ids (starting at 1 on each restart)
|
||||||
- runningIds - array of integer job ids that are running
|
- running_ids - array of integer job ids that are running
|
||||||
- finishedIds - array of integer job ids that are finished
|
- finished_ids - array of integer job ids that are finished
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -436,8 +436,8 @@ func rcJobList(ctx context.Context, in rc.Params) (out rc.Params, err error) {
|
|||||||
out = make(rc.Params)
|
out = make(rc.Params)
|
||||||
out["jobids"] = running.IDs()
|
out["jobids"] = running.IDs()
|
||||||
runningIDs, finishedIDs := running.Stats()
|
runningIDs, finishedIDs := running.Stats()
|
||||||
out["runningIds"] = runningIDs
|
out["running_ids"] = runningIDs
|
||||||
out["finishedIds"] = finishedIDs
|
out["finished_ids"] = finishedIDs
|
||||||
out["executeId"] = executeID
|
out["executeId"] = executeID
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,8 +378,8 @@ func TestRcJobList(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, out1)
|
require.NotNil(t, out1)
|
||||||
assert.Equal(t, []int64{1}, out1["jobids"], "should have job listed")
|
assert.Equal(t, []int64{1}, out1["jobids"], "should have job listed")
|
||||||
assert.Equal(t, []int64{1}, out1["runningIds"], "should have running job")
|
assert.Equal(t, []int64{1}, out1["running_ids"], "should have running job")
|
||||||
assert.Equal(t, []int64{}, out1["finishedIds"], "should not have finished job")
|
assert.Equal(t, []int64{}, out1["finished_ids"], "should not have finished job")
|
||||||
|
|
||||||
_, _, err = NewJob(ctx, longFn, rc.Params{"_async": true})
|
_, _, err = NewJob(ctx, longFn, rc.Params{"_async": true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user