@Override
    public void login(final LoginCallback prompt, final CancelCallback cancel) throws BackgroundException {
        final SoftwareVersionData version = this.softwareVersion();
        final Matcher matcher = Pattern.compile(VERSION_REGEX).matcher(version.getRestApiVersion());
        if(matcher.matches()) {
            if(new Version(matcher.group(1)).compareTo(new Version(preferences.getProperty("sds.version.lts"))) < 0) {
                throw new InteroperabilityException(
                        LocaleFactory.localizedString("DRACOON environment needs to be updated", "SDS"),
                        LocaleFactory.localizedString("Your DRACOON environment is outdated and no longer works with this application. Please contact your administrator.", "SDS"));
            }
        }
        final Credentials credentials;
        // The provided token is valid for two hours, every usage resets this period to two full hours again. Logging off invalidates the token.
        switch(SDSProtocol.Authorization.valueOf(host.getProtocol().getAuthorization())) {
            case oauth:
            case password:
                if("x-dracoon-action:oauth".equals(CYBERDUCK_REDIRECT_URI)) {
                    if(matcher.matches()) {
                        if(new Version(matcher.group(1)).compareTo(new Version("4.15.0")) >= 0) {
                            authorizationService.withRedirectUri(CYBERDUCK_REDIRECT_URI);
                        }
                    }
                    else {
                        log.warn(String.format("Failure to parse software version %s", version));
                    }
                }
                credentials = authorizationService.validate();
                break;
            default:
                credentials = host.getCredentials();
        }
        final UserAccount account;
        try {
            account = new UserApi(client).requestUserInfo(StringUtils.EMPTY, false, null);
            if(log.isDebugEnabled()) {
                log.debug(String.format("Authenticated as user %s", account));
            }
        }
        catch(ApiException e) {
            throw new SDSExceptionMappingService(nodeid).map(e);
        }
        switch(SDSProtocol.Authorization.valueOf(host.getProtocol().getAuthorization())) {
            case oauth:
                credentials.setUsername(account.getLogin());
        }
        userAccount.set(new UserAccountWrapper(account));
        requiredKeyPairVersion = this.getRequiredKeyPairVersion();
        this.unlockTripleCryptKeyPair(prompt, userAccount.get(), requiredKeyPairVersion);
        this.updateDirectS3UploadSetting();
    }
@Test(expected = LoginCanceledException.class)
    public void testLoginOAuthExpiredRefreshToken() throws Exception {
        final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new SDSProtocol())));
        final Profile profile = new ProfilePlistReader(factory).read(
                this.getClass().getResourceAsStream("/DRACOON (OAuth).cyberduckprofile"));
        final Host host = new Host(profile, "duck.dracoon.com", new Credentials(
                System.getProperties().getProperty("dracoon.user"), System.getProperties().getProperty("dracoon.key")
        ));
        final SDSSession session = new SDSSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager());
        assertNotNull(session.open(new DisabledProxyFinder(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()));
        assertTrue(session.isConnected());
        assertNotNull(session.getClient());
        session.login(new DisabledLoginCallback(), new DisabledCancelCallback());
        assertFalse(new SDSListService(session, new SDSNodeIdProvider(session)).list(new Path("/", EnumSet.of(Path.Type.directory)), new DisabledListProgressListener()).isEmpty());
    }