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

Compare commits

...

3 Commits

Author SHA1 Message Date
albertony
f838c2a2f2 jottacloud: add support for MediaMarkt Cloud as a whitelabel service
This was requested in issue #8852, after authentication was already fixed for existing
whitelabels.
2025-10-04 17:52:22 +02:00
albertony
49103c7348 jottacloud: refactor service list from map to slice to get predefined order 2025-10-04 15:26:11 +02:00
albertony
697874e399 jottacloud: added support for traditional oauth authentication also for the main service
This renames whitelabel authentication to traditional authentication and adds support for
the main Jottacloud service also here, as it can be used as an alternative to the
authentication based on personal login token for those who prefer it. Documentation
also adjusted correspondingly, and restructured the authentication section a bit more
since some of the sections that was under standard authentication in reality also
applies to the traditional authentication.
2025-10-04 15:26:11 +02:00
2 changed files with 146 additions and 95 deletions

View File

@@ -17,7 +17,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -60,7 +60,7 @@ const (
configVersion = 1 configVersion = 1
defaultTokenURL = "https://id.jottacloud.com/auth/realms/jottacloud/protocol/openid-connect/token" defaultTokenURL = "https://id.jottacloud.com/auth/realms/jottacloud/protocol/openid-connect/token"
defaultClientID = "jottacli" defaultClientID = "jottacli" // Identified as "Jottacloud CLI" in "My logged in devices"
legacyTokenURL = "https://api.jottacloud.com/auth/v1/token" legacyTokenURL = "https://api.jottacloud.com/auth/v1/token"
legacyRegisterURL = "https://api.jottacloud.com/auth/v1/register" legacyRegisterURL = "https://api.jottacloud.com/auth/v1/register"
@@ -69,27 +69,29 @@ const (
legacyConfigVersion = 0 legacyConfigVersion = 0
) )
func getWhitelabelServices() map[string]struct { type service struct {
name, domain, realm, clientID string key string
scopes []string name string
} { domain string
return map[string]struct { realm string
name string clientID string
domain string scopes []string
realm string }
clientID string
scopes []string func getServices() []service {
}{ return []service{
"telia_se": {"Telia Cloud (Sweden)", "cloud-auth.telia.se", "telia_se", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"jottacloud", "Jottacloud", "id.jottacloud.com", "jottacloud", "desktop", []string{"openid", "jotta-default", "offline_access"}}, // Chose client id "desktop" here, will be identified as "Jottacloud for Desktop" in "My logged in devices", but could have used "jottacli" here as well.
"telia_no": {"Telia Sky (Norway)", "sky-auth.telia.no", "get", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"elkjop", "Elkjøp Cloud (Norway)", "cloud.elkjop.no", "elkjop", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"tele2": {"Tele2 Cloud (Sweden)", "mittcloud-auth.tele2.se", "comhem", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"elgiganten_dk", "Elgiganten Cloud (Denmark)", "cloud.elgiganten.dk", "elgiganten", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"onlime": {"Onlime (Denmark)", "cloud-auth.onlime.dk", "onlime_wl", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"elgiganten_se", "Elgiganten Cloud (Sweden)", "cloud.elgiganten.se", "elgiganten", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"elkjop": {"Elkjøp Cloud (Norway)", "cloud.elkjop.no", "elkjop", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"elko", "ELKO Cloud (Iceland)", "cloud.elko.is", "elko", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"elgiganten_se": {"Elgiganten Cloud (Sweden)", "cloud.elgiganten.se", "elgiganten", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"gigantti", "Gigantti Cloud (Finland)", "cloud.gigantti.fi", "gigantti", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"elgiganten_dk": {"Elgiganten Cloud (Denmark)", "cloud.elgiganten.dk", "elgiganten", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"telia_se", "Telia Cloud (Sweden)", "cloud-auth.telia.se", "telia_se", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"gigantti": {"Gigantti Cloud (Finland)", "cloud.gigantti.fi", "gigantti", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"telia_no", "Telia Sky (Norway)", "sky-auth.telia.no", "get", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"elko": {"ELKO Cloud (Iceland)", "cloud.elko.is", "elko", "desktop", []string{"openid", "jotta-default", "offline_access"}}, {"tele2", "Tele2 Cloud (Sweden)", "mittcloud-auth.tele2.se", "comhem", "desktop", []string{"openid", "jotta-default", "offline_access"}},
"letsgo": {"Let's Go Cloud (Germany)", "letsgo.jotta.cloud", "letsgo", "desktop-win", []string{"openid", "offline_access"}}, {"onlime", "Onlime (Denmark)", "cloud-auth.onlime.dk", "onlime_wl", "desktop", []string{"openid", "jotta-default", "offline_access"}},
{"mediamarkt", "MediaMarkt Cloud", "mediamarkt.jottacloud.com", "mediamarkt", "desktop", []string{"openid", "jotta-default", "offline_access"}},
{"letsgo", "Let's Go Cloud (Germany)", "letsgo.jotta.cloud", "letsgo", "desktop-win", []string{"openid", "offline_access"}},
} }
} }
@@ -176,20 +178,32 @@ func Config(ctx context.Context, name string, m configmap.Mapper, conf fs.Config
} }
return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Type of authentication.`, []fs.OptionExample{{ return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Type of authentication.`, []fs.OptionExample{{
Value: "standard", Value: "standard",
Help: "Standard authentication.\nUse this if you're a normal Jottacloud user.", Help: `Standard authentication.
This is primarily supported by the official service, but may also be supported
by some of the white-label services. It is designed for command-line
applications, and you will be asked to enter a single-use personal login token
which you must manually generate from the account security settings in the
web interface of your service.`,
}, { }, {
Value: "whitelabel", Value: "traditional",
Help: "Whitelabel authentication.\nUse this if you are using the service offered by a third party such as Telia, Tele2, Onlime, Elkjøp, etc.", Help: `Traditional authentication.
This is supported by the official service and most of the white-label
services, you will be asked which service to connect to. You need to be on
a machine with an internet-connected web browser.`,
}, { }, {
Value: "legacy", Value: "legacy",
Help: "Legacy authentication.\nThis is no longer supported by any known services and not recommended for normal users.", Help: `Legacy authentication.
This is no longer supported by any known services and not recommended used.
You will be asked for your account's username and password.`,
}}) }})
case "auth_type_done": case "auth_type_done":
// Jump to next state according to config chosen // Jump to next state according to config chosen
return fs.ConfigGoto(conf.Result) return fs.ConfigGoto(conf.Result)
case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication
m.Set("configVersion", fmt.Sprint(configVersion)) m.Set("configVersion", fmt.Sprint(configVersion))
return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\nGenerate here: https://www.jottacloud.com/web/secure") return fs.ConfigInput("standard_token", "config_login_token", `Personal login token.
Generate it from the account security settings in the web interface of your
service, for the official service on https://www.jottacloud.com/web/secure.`)
case "standard_token": case "standard_token":
loginToken := conf.Result loginToken := conf.Result
m.Set(configClientID, defaultClientID) m.Set(configClientID, defaultClientID)
@@ -206,29 +220,28 @@ func Config(ctx context.Context, name string, m configmap.Mapper, conf fs.Config
return nil, fmt.Errorf("error while saving token: %w", err) return nil, fmt.Errorf("error while saving token: %w", err)
} }
return fs.ConfigGoto("choose_device") return fs.ConfigGoto("choose_device")
case "whitelabel": case "traditional":
whitelabels := getWhitelabelServices() services := getServices()
options := make([]fs.OptionExample, 0, len(whitelabels)) options := make([]fs.OptionExample, 0, len(services))
for key, val := range whitelabels { for _, service := range services {
options = append(options, fs.OptionExample{ options = append(options, fs.OptionExample{
Value: key, Value: service.key,
Help: val.name, Help: service.name,
}) })
} }
sort.Slice(options, func(i, j int) bool { return fs.ConfigChooseExclusiveFixed("traditional_type", "config_traditional",
return options[i].Help < options[j].Help
})
return fs.ConfigChooseExclusiveFixed("whitelabel_type", "config_whitelabel",
"White-label service. This decides the domain name to connect to and\nthe authentication configuration to use.", "White-label service. This decides the domain name to connect to and\nthe authentication configuration to use.",
options) options)
case "whitelabel_type": case "traditional_type":
whitelabel, ok := getWhitelabelServices()[conf.Result] services := getServices()
if !ok { i := slices.IndexFunc(services, func(s service) bool { return s.key == conf.Result })
return nil, fmt.Errorf("unexpected whitelabel %q", conf.Result) if i == -1 {
return nil, fmt.Errorf("unexpected service %q", conf.Result)
} }
service := services[i]
opts := rest.Opts{ opts := rest.Opts{
Method: "GET", Method: "GET",
RootURL: "https://" + whitelabel.domain + "/auth/realms/" + whitelabel.realm + "/.well-known/openid-configuration", RootURL: "https://" + service.domain + "/auth/realms/" + service.realm + "/.well-known/openid-configuration",
} }
var wellKnown api.WellKnown var wellKnown api.WellKnown
srv := rest.NewClient(fshttp.NewClient(ctx)) srv := rest.NewClient(fshttp.NewClient(ctx))
@@ -237,14 +250,14 @@ func Config(ctx context.Context, name string, m configmap.Mapper, conf fs.Config
return nil, fmt.Errorf("failed to get authentication provider configuration: %w", err) return nil, fmt.Errorf("failed to get authentication provider configuration: %w", err)
} }
m.Set("configVersion", fmt.Sprint(configVersion)) m.Set("configVersion", fmt.Sprint(configVersion))
m.Set(configClientID, whitelabel.clientID) m.Set(configClientID, service.clientID)
m.Set(configTokenURL, wellKnown.TokenEndpoint) m.Set(configTokenURL, wellKnown.TokenEndpoint)
return oauthutil.ConfigOut("choose_device", &oauthutil.Options{ return oauthutil.ConfigOut("choose_device", &oauthutil.Options{
OAuth2Config: &oauthutil.Config{ OAuth2Config: &oauthutil.Config{
AuthURL: wellKnown.AuthorizationEndpoint, AuthURL: wellKnown.AuthorizationEndpoint,
TokenURL: wellKnown.TokenEndpoint, TokenURL: wellKnown.TokenEndpoint,
ClientID: whitelabel.clientID, ClientID: service.clientID,
Scopes: whitelabel.scopes, Scopes: service.scopes,
RedirectURL: oauthutil.RedirectLocalhostURL, RedirectURL: oauthutil.RedirectLocalhostURL,
}, },
}) })

View File

@@ -28,60 +28,86 @@ as described [below](#whitelabel-authentication):
- Onlime - Onlime
- Onlime (onlime.dk) - Onlime (onlime.dk)
- MediaMarkt - MediaMarkt
- MediaMarkt Cloud (mediamarkt.jottacloud.com)
- Let's Go Cloud (letsgo.jotta.cloud) - Let's Go Cloud (letsgo.jotta.cloud)
Paths are specified as `remote:path` Paths are specified as `remote:path`
Paths may be as deep as required, e.g. `remote:directory/subdirectory`. Paths may be as deep as required, e.g. `remote:directory/subdirectory`.
## Authentication types ## Authentication
Some of the white-label versions uses a different authentication method than the Authentication in Jottacloud is in general based on OAuth 2.0 and OpenID
official service, and you have to choose the correct one when setting up the remote. Connect (OIDC). There are different variants to choose from, described below.
Some of the variants are only supported by the official service and not
### Standard authentication white-label services, so this must be taken into consideration when choosing.
The standard authentication method used by the official service (jottacloud.com),
as well as some of the white-label services, is based on OAuth 2.0 and OpenID
Connect (OIDC), and requires you to generate a single-use personal login token
from the account security settings in the service's web interface. Log in to your
account, go to "Settings" and then "Security", or use the direct link presented
to you by rclone when configuring the remote:
<https://www.jottacloud.com/web/secure>. Scroll down to the section "Personal login
token", and click the "Generate" button. Note that if you are using a white-label
service you probably can't use the direct link, you need to find the same page in
their dedicated web interface, and also it may be in a different location than
described above.
To access your account from multiple instances of rclone, you need to configure To access your account from multiple instances of rclone, you need to configure
each of them with a separate personal login token. E.g. you create a Jottacloud each of them separately. E.g. you create a Jottacloud remote with rclone in one
remote with rclone in one location, and copy the configuration file to a second location, and copy the configuration file to a second location where you also
location where you also want to run rclone and access the same remote. Then you want to run rclone and access the same remote. Then you need to replace the
need to replace the token for one of them, using the [config reconnect](https://rclone.org/commands/rclone_config_reconnect/) token for one of them, using the [config reconnect](https://rclone.org/commands/rclone_config_reconnect/)
command, which requires you to generate a new personal login token and supply command. For standard authentication (described below) this means you will have
as input. If you do not do this, the token may easily end up being invalidated, to generate a new personal login token and supply as input. If you do not do
resulting in both instances failing with an error message something along the this, the token may easily end up being invalidated, resulting in both
lines of: instances failing with an error message something along the lines of:
```text ```text
oauth2: cannot fetch token: 400 Bad Request oauth2: cannot fetch token: 400 Bad Request
Response: {"error":"invalid_grant","error_description":"Stale token"} Response: {"error":"invalid_grant","error_description":"Stale token"}
``` ```
When this happens, you need to replace the token as described above to be able The background for this is that OAuth tokens from Jottacloud normally have one
to use your remote again. hour expiry, after which they will be automatically refreshed by rclone.
Jottacloud will then refresh not only the access token, but also the refresh
token. Any requests using a previous refresh token will be flagged, and lead
to the stale token error. When this happens, you need to replace the token as
described above to be able to use your remote again.
All personal login tokens you have taken into use will be listed in the web Each time you are granted access with a new token, it will listed in the web
interface under "My logged in devices", and from the right side of that list interface under "My logged in devices". From the right side of that list you
you can click the "X" button to revoke individual tokens. can click the "X" button to revoke access. This will effectively disable the
refresh token, which means you will still have access using an existing access
token until that expires, but you will not be able to refresh it.
### Whitelabel authentication ### Standard
Most of the white-label versions uses a slightly different authentication flow, This is an OAuth variant designed for command-line applications. It is
where it doesn't offer the option of creating a CLI token, and the username primarily supported by the official service (jottacloud.com), but may also be
is generated internally. To setup rclone to use one of these, choose white-label supported by some of the white-label services. The specific domain name and
authentication in the setup process, and then select the specific service endpoint to connect to are found automatically (it is encoded into the supplied
in the next step. login token, described next).
When configuring a remote, you are asked to enter a single-use personal login
token, which you must manually generate from the account security settings in
the service's web interface. You do not need a web browser on the same machine
like with traditional OAuth, but need to use a web browser somewhere, and be
able to be copy the generated string into your rclone configuration session.
Log in to your account, go to "Settings" and then "Security", or use the direct
link presented to you by rclone when configuring the remote:
<https://www.jottacloud.com/web/secure>. Scroll down to the section "Personal
login token", and click the "Generate" button. Note that if you are using a
white-label service you probably can't use the direct link, you need to find
the same page in their dedicated web interface, and also it may be in a
different location than described above.
When you have successfully authenticated using a personal login token, which
means you have received a proper OAuth token, there will be an entry in the
"My logged in devices" list in the web interface. It will be listed with
application name "Jottacloud CLI".
### Traditional
Jottacloud also supports a more traditional OAuth variant. Most of the
white-label services supports this, and often only this as they do not support
personal login tokens. This method relies on pre-defined domain names and
endpoints, and rclone must therefore explicitly add any white-label services
that should be supported.
When configuring a remote, you must interactively login to an OAuth
authorization web site, and a one-time authorization code are automatically
sent back to rclone, which it uses to request a token.
Note that when setting this up, you need to be on a machine with an Note that when setting this up, you need to be on a machine with an
internet-connected web browser. If you need it on a machine where this is not internet-connected web browser. If you need it on a machine where this is not
@@ -90,14 +116,18 @@ and copy it from there. The jottacloud backend does not support the
`rclone authorize` command. See the [remote setup docs](/remote_setup) for `rclone authorize` command. See the [remote setup docs](/remote_setup) for
details. details.
### Legacy authentication When you have successfully authenticated, there will be an entry in the
"My logged in devices" list in the web interface. It will typically be listed
with application name "Jottacloud for Desktop" or similar (it depends on the
white-label service configuration).
Originally Jottacloud used an older authentication method, not based on OpenID ### Legacy
Connect, which required the username and password to be specified. Since
Jottacloud migrated to the newer method, handled by the standard authentication, Originally Jottacloud used an OAuth variant which required your account's
some white-label versions (those from Elkjøp) still used the legacy method for username and password to be specified. When Jottacloud migrated to the newer
a long time. Currently there are no known uses of this, it is still supported methods, some white-label versions (those from Elkjøp) still used this legacy
by rclone, but the support will be removed in a future version. method for a long time. Currently there are no known uses of this, it is still
supported by rclone, but the support will be removed in a future version.
## Configuration ## Configuration
@@ -151,19 +181,27 @@ Type of authentication.
Choose a number from below, or type in an existing value of type string. Choose a number from below, or type in an existing value of type string.
Press Enter for the default (standard). Press Enter for the default (standard).
/ Standard authentication. / Standard authentication.
1 | Use this if you're a normal Jottacloud user. | This is primarily supported by the official service, but may also be supported
| by some of the white-label services. It is designed for command-line
1 | applications, and you will be asked to enter a single-use personal login token
| which you must manually generate from the account security settings in the
| web interface of your service.
\ (standard) \ (standard)
/ Whitelabel authentication. / Traditional authentication.
2 | Use this if you are using the service offered by a third party such as Telia, Tele2, Onlime, Elkjøp, etc. | This is supported by the official service and most of the white-label
\ (whitelabel) 2 | services, you will be asked which service to connect to. You need to be on
| a machine with an internet-connected web browser.
\ (traditional)
/ Legacy authentication. / Legacy authentication.
3 | This is no longer supported by any known services and not recommended for normal users. 3 | This is no longer supported by any known services and not recommended used.
| You will be asked for your account's username and password.
\ (legacy) \ (legacy)
config_type> 1 config_type> 1
Option config_login_token. Option config_login_token.
Personal login token. Personal login token.
Generate here: https://www.jottacloud.com/web/secure Generate it from the account security settings in the web interface of your
service, for the official service on https://www.jottacloud.com/web/secure.
Enter a value. Enter a value.
config_login_token> <your token here> config_login_token> <your token here>