Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
584434f
Rename class.
dkocher Sep 8, 2025
6c9d38f
Extract field.
dkocher Sep 8, 2025
b4cc983
Allow lookup of property value for multiple possible keys used for ba…
dkocher Sep 8, 2025
ddce329
Add definitions for Role ARN and MFA Serial as used in ~/.aws/credent…
dkocher Sep 8, 2025
5189c20
Add support for common directive names from AWS CLI credentials confi…
dkocher Sep 8, 2025
026d27c
Add prompt for role ARN.
dkocher Sep 8, 2025
2239c1b
Add support to assume role from static credentials with prmpt for rol…
dkocher Sep 8, 2025
2e7f686
Delete unused parameter.
dkocher Sep 8, 2025
8434d2d
Comments about properties.
dkocher Sep 8, 2025
0846d16
Formatting.
dkocher Sep 8, 2025
04b9a2a
Add interceptor to assume role depending on profile configuration.
dkocher Sep 8, 2025
e3ad15a
The get() method calls refresh(tokens) but should call refresh(creden…
dkocher Sep 8, 2025
419bb77
Iterate all keys
dkocher Sep 8, 2025
c3cf1ee
Fix setting ARN for MFA and token code in request.
dkocher Sep 8, 2025
e86e713
Depend on token configurable boolean in profile.
dkocher Sep 9, 2025
36fe351
Always require OAuth Client ID.
dkocher Sep 9, 2025
8a5740c
Allow clear text entry field.
dkocher Sep 9, 2025
bc44327
Allow cleartext entry for ARN.
dkocher Sep 9, 2025
950c877
Set placeholder text.
dkocher Sep 9, 2025
fb8ab24
Prompt for MFA Amazon Resource Name (ARN) when missing.
dkocher Sep 10, 2025
e1ee21c
Review prompts.
dkocher Sep 10, 2025
f949b4c
Set random session name.
dkocher Sep 10, 2025
fd569a1
Formatting.
dkocher Sep 10, 2025
ee507f1
Move to service.
dkocher Sep 10, 2025
f7b85a3
Use basic implementations.
dkocher Sep 10, 2025
97c0122
Set placeholder text.
dkocher Sep 10, 2025
825bb07
Attempt to read OAuth Ids from user set in bookmark properties.
dkocher Sep 10, 2025
16bb976
Review prompts.
dkocher Sep 10, 2025
89ba710
Reuse implementation.
dkocher Sep 11, 2025
8978a91
Return user id.
dkocher Sep 11, 2025
3dd62f7
Log failures.
dkocher Sep 11, 2025
3c663bd
Only return configuration but do not make any service requests.
dkocher Sep 11, 2025
99c5b99
Formatting.
dkocher Sep 11, 2025
97d65ec
Use basic implementations.
dkocher Sep 11, 2025
b091af3
Use builder pattern.
dkocher Sep 11, 2025
27564dc
Revert "Reuse implementation."
dkocher Sep 11, 2025
a541e72
Move to interface.
dkocher Sep 11, 2025
64ae9ca
Javadoc.
dkocher Sep 12, 2025
5bfa1b3
Move up to interface as default implementations.
dkocher Sep 12, 2025
76ec7b6
Javadoc.
dkocher Sep 12, 2025
53523a4
Implement preferences reader interface.
dkocher Sep 12, 2025
05025db
Delete unused class.
dkocher Sep 12, 2025
48e9f1c
Allow to read preferences from multiple sources.
dkocher Sep 12, 2025
c6399e7
Add copy constructor.
dkocher Sep 12, 2025
85e52fb
Use lambda.
dkocher Sep 12, 2025
64d263f
Do not set credentials in client but always retrieve from authenticat…
dkocher Sep 12, 2025
acd33b3
Do not allow custom client.
dkocher Sep 12, 2025
517dd4b
Make empty tokens return expired status.
dkocher Sep 15, 2025
6fd2816
Logging.
dkocher Sep 17, 2025
0c9ce03
Rename and change parameter.
dkocher Sep 17, 2025
8c90889
Logging.
dkocher Sep 18, 2025
7ef6900
Logging.
dkocher Sep 18, 2025
26947da
Logging.
dkocher Sep 18, 2025
aa3ef0e
Copy properties.
dkocher Sep 18, 2025
17bc665
Return "default" profile when no match.
dkocher Sep 18, 2025
6b54c87
Use factory.
dkocher Sep 18, 2025
e1507ea
Use custom configurator.
dkocher Sep 18, 2025
1e2b2a5
Also set static credentials.
dkocher Sep 18, 2025
1a08570
Refactor role-based authentication. Add support for `GetSessionToken`…
dkocher Sep 18, 2025
be4abde
Make profile configuration explicit to require assuming role or MFA.
dkocher Sep 19, 2025
7faf232
Review input validation.
dkocher Sep 20, 2025
bc7a5ee
Remove unused parameter.
dkocher Sep 20, 2025
d30ea78
Delete unused.
dkocher Sep 20, 2025
14c18e6
Typo.
dkocher Sep 20, 2025
f44e27a
Do not return credentials of default profile when username is set.
dkocher Sep 20, 2025
7bb5657
Revert "Also set static credentials."
dkocher Sep 20, 2025
7e5a594
Use regular authenticator for presigned URLs.
dkocher Sep 20, 2025
6421ba1
Fix test.
dkocher Sep 21, 2025
8314814
Fix test.
dkocher Sep 21, 2025
d538cd4
Ensure lookup of credentials for S3 (Credentials from AWS Command Lin…
dkocher Sep 21, 2025
4bab3f9
Fix verification of credentials after auto configuration.
dkocher Sep 21, 2025
d96fd61
Validate tokens by comparing with empty set.
dkocher Sep 21, 2025
70255b9
Delegate validation of tokens.
dkocher Sep 21, 2025
f54d8c5
Keep already configured keys read from AWS ClI configuration.
dkocher Sep 21, 2025
672f2e1
Extract common class.
dkocher Sep 22, 2025
fb36de5
Only require password callback.
dkocher Sep 22, 2025
a1ee990
Handle static credentials input.
dkocher Sep 22, 2025
37fdc35
Allow to continue with no MFA serial.
dkocher Sep 22, 2025
0164f55
Prompt when profile allows input for ARNs.
dkocher Sep 22, 2025
27d165c
Do not prompt with no ARN set for device.
dkocher Sep 22, 2025
be36867
Add checks for empty input when user chooses to skip prompt.
dkocher Sep 23, 2025
38eeefb
Remove unused parameter.
dkocher Sep 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions core/src/main/java/ch/cyberduck/core/AbstractProtocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -292,6 +291,16 @@ public boolean isPrivateKeyConfigurable() {
return false;
}

@Override
public boolean isRoleConfigurable() {
return false;
}

@Override
public boolean isMultiFactorConfigurable() {
return false;
}

@Override
public boolean isUTCTimezone() {
return true;
Expand Down Expand Up @@ -445,23 +454,27 @@ public boolean validate(final Credentials credentials, final LoginOptions option
}
}
if(options.password) {
if(credentials.isAnonymousLogin()) {
return true;
}
switch(this.getType()) {
case ftp:
case dav:
return Objects.nonNull(credentials.getPassword());
// Allow blank password
return credentials.isPasswordAuthentication(true);
case sftp:
// SFTP agent auth requires no password and no private key selection
return true;
default:
return StringUtils.isNotBlank(credentials.getPassword());
return credentials.isPasswordAuthentication();
}
}
if(options.oauth) {
// Always refresh tokens in login
// Always authentication with no tokens preset
return true;
}
if(options.token) {
return StringUtils.isNotBlank(credentials.getToken());
return credentials.isTokenAuthentication();
}
return true;
}
Expand Down

This file was deleted.

45 changes: 42 additions & 3 deletions core/src/main/java/ch/cyberduck/core/Credentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
*/

import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.preferences.PreferencesReader;

import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
* Stores the login credentials
*/
public class Credentials implements Comparable<Credentials> {
public class Credentials implements Comparable<Credentials>, PreferencesReader {

/**
* The login name
Expand All @@ -38,13 +41,26 @@ public class Credentials implements Comparable<Credentials> {
* The login password
*/
private String password = StringUtils.EMPTY;
/**
* Temporary access tokens
*/
private TemporaryAccessTokens tokens = TemporaryAccessTokens.EMPTY;
/**
* OIDC tokens
*/
private OAuthTokens oauth = OAuthTokens.EMPTY;
/**
* Custom protocol dependent properties related to authentication
*/
private final Map<String, String> properties = new HashMap<>();

/**
* Private key identity for SSH public key authentication.
*/
private Local identity;
/**
* Passphrase for private key identity for SSH public key authentication.
*/
private String identityPassphrase = StringUtils.EMPTY;

/**
Expand All @@ -69,6 +85,7 @@ public Credentials(final Credentials copy) {
this.password = copy.password;
this.tokens = copy.tokens;
this.oauth = copy.oauth;
this.properties.putAll(copy.properties);
this.identity = copy.identity;
this.identityPassphrase = copy.identityPassphrase;
this.certificate = copy.certificate;
Expand Down Expand Up @@ -199,15 +216,26 @@ public boolean isAnonymousLogin() {
}

public boolean isPasswordAuthentication() {
return this.isPasswordAuthentication(false);
}

/**
* @param allowblank Allow blank password
*/
public boolean isPasswordAuthentication(final boolean allowblank) {
if(allowblank) {
// Allow blank password
return Objects.nonNull(password);
}
return StringUtils.isNotBlank(password);
}

public boolean isTokenAuthentication() {
return StringUtils.isNotBlank(tokens.getSessionToken());
return tokens != TemporaryAccessTokens.EMPTY;
}

public boolean isOAuthAuthentication() {
return oauth.validate();
return oauth != OAuthTokens.EMPTY;
}

/**
Expand Down Expand Up @@ -273,6 +301,16 @@ public boolean isCertificateAuthentication() {
return true;
}

public Credentials withProperty(final String key, final String value) {
properties.put(key, value);
return this;
}

@Override
public String getProperty(final String key) {
return properties.get(key);
}

/**
* @param protocol The protocol to verify against.
* @param options Options
Expand Down Expand Up @@ -337,6 +375,7 @@ public String toString() {
sb.append(", tokens='").append(tokens).append('\'');
sb.append(", oauth='").append(oauth).append('\'');
sb.append(", identity=").append(identity);
sb.append(", properties=").append(properties);
sb.append('}');
return sb.toString();
}
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/java/ch/cyberduck/core/Host.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import ch.cyberduck.core.ftp.FTPConnectMode;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.preferences.PreferencesReader;
import ch.cyberduck.core.serializer.Serializer;

import org.apache.commons.lang3.StringUtils;
Expand All @@ -31,7 +32,7 @@
import java.util.Set;
import java.util.TimeZone;

public class Host implements Serializable, Comparable<Host> {
public class Host implements Serializable, Comparable<Host>, PreferencesReader {

/**
* The credentials to authenticate with for the CDN
Expand Down Expand Up @@ -347,6 +348,7 @@ public Credentials getCredentials() {
}

public void setCredentials(final Credentials credentials) {
log.debug("Setting credentials for {} to {}", this, credentials);
this.credentials = credentials;
}

Expand Down Expand Up @@ -527,11 +529,15 @@ public void setTimezone(final TimeZone timezone) {
* @param key Property name
* @return Value for property key
*/
@Override
public String getProperty(final String key) {
final Map<String, String> overrides = this.getCustom();
if(overrides.containsKey(key)) {
return overrides.get(key);
}
if(credentials.getProperty(key) != null) {
return credentials.getProperty(key);
}
return protocol.getProperties().get(key);
}

Expand Down
28 changes: 13 additions & 15 deletions core/src/main/java/ch/cyberduck/core/KeychainLoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import ch.cyberduck.core.exception.LocalAccessDeniedException;
import ch.cyberduck.core.exception.LoginCanceledException;
import ch.cyberduck.core.exception.LoginFailureException;
import ch.cyberduck.core.proxy.ProxyFinder;
import ch.cyberduck.core.threading.CancelCallback;

import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -61,8 +60,7 @@ public void validate(final Host bookmark, final LoginCallback prompt, final Logi
if(StringUtils.isNotBlank(password)) {
log.info("Fetched password from keychain for {}", bookmark);
// No need to reinsert found password to the keychain.
credentials.setSaved(false);
credentials.setPassword(password);
credentials.withPassword(password).setSaved(false);
}
}
}
Expand All @@ -72,8 +70,7 @@ public void validate(final Host bookmark, final LoginCallback prompt, final Logi
if(StringUtils.isNotBlank(token)) {
log.info("Fetched token from keychain for {}", bookmark);
// No need to reinsert found token to the keychain.
credentials.setSaved(false);
credentials.setToken(token);
credentials.withToken(token).setSaved(false);
}
}
}
Expand All @@ -82,26 +79,27 @@ public void validate(final Host bookmark, final LoginCallback prompt, final Logi
if(StringUtils.isNotBlank(passphrase)) {
log.info("Fetched private key passphrase from keychain for {}", bookmark);
// No need to reinsert found token to the keychain.
credentials.setSaved(false);
credentials.setIdentityPassphrase(passphrase);
credentials.withIdentityPassphrase(passphrase).setSaved(false);
}
}
if(options.oauth) {
final OAuthTokens tokens = keychain.findOAuthTokens(bookmark);
if(tokens.validate()) {
log.info("Fetched OAuth token from keychain for {}", bookmark);
// No need to reinsert found token to the keychain.
credentials.setSaved(tokens.isExpired());
credentials.setOauth(tokens);
credentials.withOauth(tokens).setSaved(tokens.isExpired());
}
}
}
if(!credentials.validate(bookmark.getProtocol(), options)) {
final CredentialsConfigurator configurator = bookmark.getProtocol().getFeature(CredentialsConfigurator.class);
final CredentialsConfigurator configurator = CredentialsConfiguratorFactory.get(bookmark.getProtocol());
log.debug("Auto configure credentials with {}", configurator);
bookmark.setCredentials(configurator.configure(bookmark));
}
if(!credentials.validate(bookmark.getProtocol(), options)) {
final Credentials configuration = configurator.configure(bookmark);
if(configuration.validate(bookmark.getProtocol(), options)) {
bookmark.setCredentials(configuration);
log.info("Auto configured credentials {} for {}", configuration, bookmark);
return;
}
final StringAppender message = new StringAppender();
if(options.password) {
message.append(MessageFormat.format(LocaleFactory.localizedString(
Expand Down Expand Up @@ -157,7 +155,7 @@ public boolean prompt(final Host bookmark, final String message, final LoginCall
}

@Override
public boolean authenticate(final ProxyFinder proxy, final Session session, final ProgressListener listener,
public boolean authenticate(final Session<?> session, final ProgressListener listener,
final LoginCallback prompt, final CancelCallback cancel) throws BackgroundException {
final Host bookmark = session.getHost();
final Credentials credentials = bookmark.getCredentials();
Expand Down Expand Up @@ -187,7 +185,7 @@ public boolean authenticate(final ProxyFinder proxy, final Session session, fina
c.initCause(e);
}
catch(IllegalArgumentException | IllegalStateException r) {
log.warn("Ignore error {} initializing faiulre {} with cause {}", r, e, c);
log.warn("Ignore error {} initializing failure {} with cause {}", r, e, c);
}
throw c;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,19 @@ public void connect(final Session<?> session, final CancelCallback cancel) throw
}
// Login
try {
this.authenticate(proxy, session, cancel);
this.authenticate(session, cancel);
}
catch(BackgroundException e) {
this.close(session);
throw e;
}
}

private void authenticate(final ProxyFinder proxy, final Session session, final CancelCallback callback) throws BackgroundException {
if(!login.authenticate(proxy, session, listener, prompt, callback)) {
private void authenticate(final Session<?> session, final CancelCallback callback) throws BackgroundException {
if(!login.authenticate(session, listener, prompt, callback)) {
if(session.isConnected()) {
// Next attempt with updated credentials but cancel when prompt is dismissed
this.authenticate(proxy, session, callback);
this.authenticate(session, callback);
}
else {
// Reconnect and next attempt with updated credentials
Expand Down
8 changes: 3 additions & 5 deletions core/src/main/java/ch/cyberduck/core/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@
import ch.cyberduck.core.exception.ConnectionCanceledException;
import ch.cyberduck.core.exception.LoginCanceledException;
import ch.cyberduck.core.exception.LoginFailureException;
import ch.cyberduck.core.proxy.ProxyFinder;
import ch.cyberduck.core.threading.CancelCallback;

public interface LoginService {
/**
* Obtain password from keychain or prompt panel
* Obtain password from password store or prompt user for input
*
* @param bookmark Credentials
* @param pompt Login prompt
Expand All @@ -37,14 +36,13 @@ public interface LoginService {
/**
* Login and prompt on failure
*
* @param proxy Proxy configuration
* @param session Session
* @param listener Authentication message callback
* @param prompt Login prompt
* @param prompt Login prompt
* @param cancel Cancel callback while authentication is in progress
* @return False if authentication fails
* @throws LoginCanceledException Login prompt canceled by user
* @throws LoginFailureException Login attempt failed
*/
boolean authenticate(ProxyFinder proxy, Session session, ProgressListener listener, LoginCallback prompt, CancelCallback cancel) throws BackgroundException;
boolean authenticate(Session<?> session, ProgressListener listener, LoginCallback prompt, CancelCallback cancel) throws BackgroundException;
}
2 changes: 1 addition & 1 deletion core/src/main/java/ch/cyberduck/core/OAuthTokens.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.Objects;

public final class OAuthTokens {
public static final OAuthTokens EMPTY = new OAuthTokens(null, null, Long.MAX_VALUE, null);
public static final OAuthTokens EMPTY = new OAuthTokens(null, null, -1L, null);

private final String accessToken;
private final String refreshToken;
Expand Down
Loading
Loading