1
0
mirror of https://github.com/rclone/rclone.git synced 2025-12-06 00:03:32 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Nick Craig-Wood
be2c44f5af rc: config/unlock: rename parameter to configPassword accept old as well
We accidentally added a non `camelCase` parameter to the rc
(`config_password`)- this fixes it (to `configPassword`) but accepts
the old name too as it has been in a release.
2025-11-20 16:09:25 +00:00
Nick Craig-Wood
1db0f51be4 rc: correct names of parameters in job/list output
These were accidentally committed as snake_case whereas we use
camelCase elsewhere.

This corrects the issue before the first release in v1.72.0
2025-11-20 15:47:51 +00:00
hunshcn
6440052fbd s3: fix single file copying behavior with low permission - Fixes #8975 2025-11-18 17:01:07 +00:00
Nick Craig-Wood
4afb59bc93 docs: onedrive: note how to backup up any user's data 2025-11-18 16:21:06 +00:00
Nick Craig-Wood
0343670375 Add Dominik Sander to contributors 2025-11-18 16:21:06 +00:00
Nick Craig-Wood
5b2b372ba9 Add jijamik to contributors 2025-11-18 16:21:06 +00:00
Dominik Sander
08c35ae741 box: allow to configure with config file contents
Especially when using rclone via rc it is helpful to configure the box
backend using the contents of the config file instead of heaving to
upload the file to the server that is running rclone.
2025-11-18 16:09:06 +00:00
8 changed files with 79 additions and 30 deletions

View File

@@ -87,13 +87,11 @@ func init() {
Description: "Box",
NewFs: NewFs,
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")
var err error
// If using box config.json, use JWT auth
if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" {
err = refreshJWTToken(ctx, jsonFile, boxSubType, name, m)
if usesJWTAuth(m) {
err = refreshJWTToken(ctx, name, m)
if err != nil {
return nil, fmt.Errorf("failed to configure token with jwt authentication: %w", err)
}
@@ -114,6 +112,11 @@ func init() {
}, {
Name: "box_config_file",
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",
Help: "Box App Primary Access Token\n\nLeave blank normally.",
@@ -184,9 +187,17 @@ See: https://developer.box.com/guides/authentication/jwt/as-user/
})
}
func refreshJWTToken(ctx context.Context, jsonFile string, boxSubType string, name string, m configmap.Mapper) error {
jsonFile = env.ShellExpand(jsonFile)
boxConfig, err := getBoxConfig(jsonFile)
func usesJWTAuth(m configmap.Mapper) bool {
jsonFile, okFile := m.Get("box_config_file")
jsonFileCredentials, okCredentials := m.Get("config_credentials")
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 {
return fmt.Errorf("get box config: %w", err)
}
@@ -205,12 +216,19 @@ func refreshJWTToken(ctx context.Context, jsonFile string, boxSubType string, na
return err
}
func getBoxConfig(configFile string) (boxConfig *api.ConfigJSON, err error) {
file, err := os.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("box: failed to read Box config: %w", err)
func getBoxConfig(m configmap.Mapper) (boxConfig *api.ConfigJSON, err error) {
configFileCredentials, _ := m.Get("config_credentials")
configFileBytes := []byte(configFileCredentials)
if configFileCredentials == "" {
configFile, _ := m.Get("box_config_file")
configFileBytes, err = os.ReadFile(configFile)
if err != nil {
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 {
return nil, fmt.Errorf("box: failed to parse Box config: %w", err)
}
@@ -485,15 +503,12 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
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 using box config.json and JWT, renewing should just refresh the token and
// should do so whether there are uploads pending or not.
if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" {
if usesJWTAuth(m) {
f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
err := refreshJWTToken(ctx, jsonFile, boxSubType, name, m)
err := refreshJWTToken(ctx, name, m)
return err
})
f.tokenRenewer.Start()

View File

@@ -1648,11 +1648,14 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
newRoot, leaf := path.Split(oldRoot)
f.setRoot(newRoot)
_, err := f.NewObject(ctx, leaf)
if err != nil {
if errors.Is(err, fs.ErrorObjectNotFound) {
// File doesn't exist or is a directory so return old f
f.setRoot(oldRoot)
return f, nil
}
if err != nil {
return nil, err
}
// return an error with an fs which points to the parent
return f, fs.ErrorIsFile
}

View File

@@ -1045,3 +1045,5 @@ put them back in again. -->
- n4n5 <its.just.n4n5@gmail.com>
- aliaj1 <ali19961@gmail.com>
- Sean Turner <30396892+seanturner026@users.noreply.github.com>
- jijamik <30904953+jijamik@users.noreply.github.com>
- Dominik Sander <git@dsander.de>

View File

@@ -221,6 +221,18 @@ 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.
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
anyone with the *Client ID* and *Client Secret* can access your
OneDrive files. Take care to safeguard these credentials.

View File

@@ -20,7 +20,7 @@ Unlocks the config file if it is locked.
Parameters:
- 'config_password' - password to unlock the config file
- 'configPassword' - password to unlock the config file
A good idea is to disable AskPassword before making this call
`,
@@ -30,9 +30,13 @@ A good idea is to disable AskPassword before making this call
// Unlock the config file
// A good idea is to disable AskPassword before making this call
func rcConfigPassword(ctx context.Context, in rc.Params) (out rc.Params, err error) {
configPass, err := in.GetString("config_password")
configPass, err := in.GetString("configPassword")
if err != nil {
return nil, err
var err2 error
configPass, err2 = in.GetString("config_password") // backwards compat
if err2 != nil {
return nil, err
}
}
if SetConfigPassword(configPass) != nil {
return nil, errors.New("failed to set config password")

View File

@@ -215,13 +215,26 @@ func TestRcPaths(t *testing.T) {
func TestRcConfigUnlock(t *testing.T) {
call := rc.Calls.Get("config/unlock")
assert.NotNil(t, call)
in := rc.Params{
"config_password": "test",
"configPassword": "test",
}
out, err := call.Fn(context.Background(), in)
require.NoError(t, err)
assert.Nil(t, err)
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)
}

View File

@@ -425,8 +425,8 @@ Results:
- executeId - string id of rclone executing (change after restart)
- jobids - array of integer job ids (starting at 1 on each restart)
- running_ids - array of integer job ids that are running
- finished_ids - array of integer job ids that are finished
- runningIds - array of integer job ids that are running
- finishedIds - 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["jobids"] = running.IDs()
runningIDs, finishedIDs := running.Stats()
out["running_ids"] = runningIDs
out["finished_ids"] = finishedIDs
out["runningIds"] = runningIDs
out["finishedIds"] = finishedIDs
out["executeId"] = executeID
return out, nil
}

View File

@@ -378,8 +378,8 @@ func TestRcJobList(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, out1)
assert.Equal(t, []int64{1}, out1["jobids"], "should have job listed")
assert.Equal(t, []int64{1}, out1["running_ids"], "should have running job")
assert.Equal(t, []int64{}, out1["finished_ids"], "should not have finished job")
assert.Equal(t, []int64{1}, out1["runningIds"], "should have running job")
assert.Equal(t, []int64{}, out1["finishedIds"], "should not have finished job")
_, _, err = NewJob(ctx, longFn, rc.Params{"_async": true})
assert.NoError(t, err)