From 8ef758d6968e0558ff7b5c115a30548bad0ca04b Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 22 Nov 2023 21:35:26 +0100 Subject: [PATCH] Save new tokens using OAuth Client ID as prefix. Fix #15284. --- .../core/CredentialManagerPasswordStore.cs | 94 ++++++++----- .../core/DefaultHostPasswordStore.java | 130 ++++++++++-------- 2 files changed, 135 insertions(+), 89 deletions(-) diff --git a/core/src/main/csharp/ch/cyberduck/core/CredentialManagerPasswordStore.cs b/core/src/main/csharp/ch/cyberduck/core/CredentialManagerPasswordStore.cs index f6da6922965..8027c258b63 100644 --- a/core/src/main/csharp/ch/cyberduck/core/CredentialManagerPasswordStore.cs +++ b/core/src/main/csharp/ch/cyberduck/core/CredentialManagerPasswordStore.cs @@ -21,8 +21,11 @@ using ch.cyberduck.core.preferences; using Ch.Cyberduck.Core.CredentialManager; using org.apache.logging.log4j; +using org.apache.logging.log4j.core.net; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; using System.Net; using System.Text; using static Windows.Win32.Security.Credentials.CRED_PERSIST; @@ -61,9 +64,12 @@ public override void delete(Host bookmark) logger.info(string.Format("Delete password for bookmark {0}", bookmark)); } var target = ToUri(bookmark); - if (!WinCredentialManager.RemoveCredentials(target.AbsoluteUri)) + foreach (Uri descriptor in target) { - base.delete(bookmark); + if (!WinCredentialManager.RemoveCredentials(descriptor.AbsoluteUri)) + { + base.delete(bookmark); + } } } @@ -86,7 +92,7 @@ public override void deletePassword(Scheme scheme, int port, string hostName, st public override string findLoginPassword(Host bookmark) { - var target = ToUri(bookmark); + var target = ToUri(bookmark)[0]; var cred = WinCredentialManager.GetCredentials(target.AbsoluteUri); if (!string.IsNullOrWhiteSpace(cred.Password)) { @@ -101,7 +107,7 @@ public override string findLoginToken(Host bookmark) { logger.info(string.Format("Fetching login token from keychain for {0}", bookmark)); } - var target = ToUri(bookmark); + var target = ToUri(bookmark)[0]; var cred = WinCredentialManager.GetCredentials(target.AbsoluteUri); if (cred.Attributes is Dictionary attrs && attrs.TryGetValue("Token", out var token) @@ -119,21 +125,30 @@ public override OAuthTokens findOAuthTokens(Host bookmark) logger.info(string.Format("Fetching OAuth tokens from keychain for {0}", bookmark)); } var target = ToUri(bookmark); - var cred = WinCredentialManager.GetCredentials(target.AbsoluteUri); - if (cred.Attributes is Dictionary attrs - && attrs.TryGetValue("OAuth Access Token", out var accessToken)) + foreach(Uri descriptor in target) { - attrs.TryGetValue("OAuth Refresh Token", out var refreshToken); - attrs.TryGetValue("OIDC Id Token", out var idToken); - long expiry = default; - if (attrs.TryGetValue("OAuth Expiry", out var expiryValue)) + var cred = WinCredentialManager.GetCredentials(descriptor.AbsoluteUri); + if (cred.Attributes is Dictionary attrs) { - long.TryParse(expiryValue, out expiry); + attrs.TryGetValue("OAuth Access Token", out var accessToken); + attrs.TryGetValue("OAuth Refresh Token", out var refreshToken); + attrs.TryGetValue("OIDC Id Token", out var idToken); + long expiry = default; + if (attrs.TryGetValue("OAuth Expiry", out var expiryValue)) + { + long.TryParse(expiryValue, out expiry); + } + OAuthTokens tokens = new(accessToken, refreshToken, new(expiry), idToken); + if(tokens.validate()) + { + return tokens; + } + // Continue } - return new(accessToken, refreshToken, new(expiry), idToken); - } - return base.findOAuthTokens(bookmark); + return base.findOAuthTokens(bookmark); + } + return OAuthTokens.EMPTY; } public override string findPrivateKeyPassphrase(Host bookmark) @@ -142,7 +157,7 @@ public override string findPrivateKeyPassphrase(Host bookmark) { logger.info(string.Format("Fetching private key passphrase from keychain for {0}", bookmark)); } - var target = ToUri(bookmark); + var target = ToUri(bookmark)[0]; var cred = WinCredentialManager.GetCredentials(target.AbsoluteUri); if (cred.Attributes is Dictionary attrs && attrs.TryGetValue("Private Key Passphrase", out var passphrase) @@ -172,7 +187,7 @@ public override void save(Host bookmark) { logger.info(string.Format("Add password for bookmark {0}", bookmark)); } - var target = ToUri(bookmark); + var target = ToUri(bookmark)[0]; var credential = bookmark.getCredentials(); var winCred = new WindowsCredentialManagerCredential( @@ -209,29 +224,42 @@ public override void save(Host bookmark) } } - private static Uri ToUri(Host bookmark) + private static Uri[] ToUri(Host bookmark) { - var protocol = bookmark.getProtocol(); - var credentials = bookmark.getCredentials(); - - var targetBuilder = new UriBuilder(PreferencesFactory.get().getProperty("application.container.name"), string.Empty); - var pathBuilder = new StringBuilder(); - pathBuilder.Append(protocol.getIdentifier()); - if (protocol.isHostnameConfigurable() || !(protocol.isTokenConfigurable() || protocol.isOAuthConfigurable())) + Collection descriptors = new(); + foreach(string descriptor in ToDescriptor(bookmark)) { - pathBuilder.Append(":" + bookmark.getHostname()); - if (protocol.isPortConfigurable() && !Equals(protocol.getDefaultPort(), bookmark.getPort())) + var protocol = bookmark.getProtocol(); + var credentials = bookmark.getCredentials(); + + var targetBuilder = new UriBuilder(PreferencesFactory.get().getProperty("application.container.name"), string.Empty); + var pathBuilder = new StringBuilder(); + pathBuilder.Append(descriptor); + if (protocol.isHostnameConfigurable() || !(protocol.isTokenConfigurable() || protocol.isOAuthConfigurable())) + { + pathBuilder.Append(":" + bookmark.getHostname()); + if (protocol.isPortConfigurable() && !Equals(protocol.getDefaultPort(), bookmark.getPort())) + { + pathBuilder.Append(":" + bookmark.getPort()); + } + } + targetBuilder.Path = pathBuilder.ToString(); + if (!string.IsNullOrWhiteSpace(credentials.getUsername())) { - pathBuilder.Append(":" + bookmark.getPort()); + targetBuilder.Query = "user=" + credentials.getUsername(); } + descriptors.Add(targetBuilder.Uri); } - targetBuilder.Path = pathBuilder.ToString(); - if (!string.IsNullOrWhiteSpace(credentials.getUsername())) + return descriptors.ToArray(); + } + + private static string[] ToDescriptor(Host bookmark) + { + if(bookmark.getProtocol().isOAuthConfigurable()) { - targetBuilder.Query = "user=" + credentials.getUsername(); + return new string[] { bookmark.getProtocol().getOAuthClientId(), bookmark.getProtocol().getIdentifier() }; } - - return targetBuilder.Uri; + return new string[] { bookmark.getProtocol().getIdentifier() }; } } } diff --git a/core/src/main/java/ch/cyberduck/core/DefaultHostPasswordStore.java b/core/src/main/java/ch/cyberduck/core/DefaultHostPasswordStore.java index e83e63852a9..3450378851a 100644 --- a/core/src/main/java/ch/cyberduck/core/DefaultHostPasswordStore.java +++ b/core/src/main/java/ch/cyberduck/core/DefaultHostPasswordStore.java @@ -148,23 +148,30 @@ public OAuthTokens findOAuthTokens(final Host bookmark) { if(log.isInfoEnabled()) { log.info(String.format("Fetching OAuth tokens from keychain for %s", bookmark)); } - final String prefix = this.getOAuthPrefix(bookmark); - final String hostname = this.getOAuthHostname(bookmark); - try { - final String expiry = this.getPassword(this.getOAuthHostname(bookmark), String.format("%s OAuth2 Token Expiry", prefix)); - return (new OAuthTokens( - this.getPassword(bookmark.getProtocol().getScheme(), this.getOAuthPort(bookmark), hostname, - String.format("%s OAuth2 Access Token", prefix)), - this.getPassword(bookmark.getProtocol().getScheme(), this.getOAuthPort(bookmark), hostname, - String.format("%s OAuth2 Refresh Token", prefix)), - expiry != null ? Long.parseLong(expiry) : -1L, - this.getPassword(bookmark.getProtocol().getScheme(), this.getOAuthPort(bookmark), hostname, - String.format("%s OIDC Id Token", prefix)))); - } - catch(LocalAccessDeniedException e) { - log.warn(String.format("Failure %s searching in keychain", e)); - return OAuthTokens.EMPTY; + final String[] descriptors = this.getOAuthPrefix(bookmark); + for(String prefix : descriptors) { + final String hostname = this.getOAuthHostname(bookmark); + try { + final String expiry = this.getPassword(this.getOAuthHostname(bookmark), String.format("%s OAuth2 Token Expiry", prefix)); + final OAuthTokens tokens = new OAuthTokens( + this.getPassword(bookmark.getProtocol().getScheme(), this.getOAuthPort(bookmark), hostname, + String.format("%s OAuth2 Access Token", prefix)), + this.getPassword(bookmark.getProtocol().getScheme(), this.getOAuthPort(bookmark), hostname, + String.format("%s OAuth2 Refresh Token", prefix)), + expiry != null ? Long.parseLong(expiry) : -1L, + this.getPassword(bookmark.getProtocol().getScheme(), this.getOAuthPort(bookmark), hostname, + String.format("%s OIDC Id Token", prefix))); + if(tokens.validate()) { + return tokens; + } + // Continue + } + catch(LocalAccessDeniedException e) { + log.warn(String.format("Failure %s searching in keychain", e)); + return OAuthTokens.EMPTY; + } } + return OAuthTokens.EMPTY; } protected String getOAuthHostname(final Host bookmark) { @@ -181,11 +188,17 @@ protected int getOAuthPort(final Host bookmark) { return Scheme.valueOf(URI.create(bookmark.getProtocol().getOAuthTokenUrl()).getScheme()).getPort(); } - private String getOAuthPrefix(final Host bookmark) { + private String[] getOAuthPrefix(final Host bookmark) { if(StringUtils.isNotBlank(bookmark.getCredentials().getUsername())) { - return String.format("%s (%s)", bookmark.getProtocol().getDescription(), bookmark.getCredentials().getUsername()); - } - return bookmark.getProtocol().getDescription(); + return new String[]{ + String.format("%s (%s)", bookmark.getProtocol().getOAuthClientId(), bookmark.getCredentials().getUsername()), + String.format("%s (%s)", bookmark.getProtocol().getDescription(), bookmark.getCredentials().getUsername()) + }; + } + return new String[]{ + bookmark.getProtocol().getOAuthClientId(), + bookmark.getProtocol().getDescription() + }; } @Override @@ -221,26 +234,29 @@ public void save(final Host bookmark) throws LocalAccessDeniedException { credentials.getToken()); } if(credentials.isOAuthAuthentication()) { - final String prefix = this.getOAuthPrefix(bookmark); - if(StringUtils.isNotBlank(credentials.getOauth().getAccessToken())) { - this.addPassword(bookmark.getProtocol().getScheme(), - this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), - String.format("%s OAuth2 Access Token", prefix), credentials.getOauth().getAccessToken()); - } - if(StringUtils.isNotBlank(credentials.getOauth().getRefreshToken())) { - this.addPassword(bookmark.getProtocol().getScheme(), - this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), - String.format("%s OAuth2 Refresh Token", prefix), credentials.getOauth().getRefreshToken()); - } - // Save expiry - if(credentials.getOauth().getExpiryInMilliseconds() != null) { - this.addPassword(this.getOAuthHostname(bookmark), String.format("%s OAuth2 Token Expiry", prefix), - String.valueOf(credentials.getOauth().getExpiryInMilliseconds())); - } - if(StringUtils.isNotBlank(credentials.getOauth().getIdToken())) { - this.addPassword(bookmark.getProtocol().getScheme(), - this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), - String.format("%s OIDC Id Token", prefix), credentials.getOauth().getIdToken()); + final String[] descriptors = this.getOAuthPrefix(bookmark); + for(String prefix : descriptors) { + if(StringUtils.isNotBlank(credentials.getOauth().getAccessToken())) { + this.addPassword(bookmark.getProtocol().getScheme(), + this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), + String.format("%s OAuth2 Access Token", prefix), credentials.getOauth().getAccessToken()); + } + if(StringUtils.isNotBlank(credentials.getOauth().getRefreshToken())) { + this.addPassword(bookmark.getProtocol().getScheme(), + this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), + String.format("%s OAuth2 Refresh Token", prefix), credentials.getOauth().getRefreshToken()); + } + // Save expiry + if(credentials.getOauth().getExpiryInMilliseconds() != null) { + this.addPassword(this.getOAuthHostname(bookmark), String.format("%s OAuth2 Token Expiry", prefix), + String.valueOf(credentials.getOauth().getExpiryInMilliseconds())); + } + if(StringUtils.isNotBlank(credentials.getOauth().getIdToken())) { + this.addPassword(bookmark.getProtocol().getScheme(), + this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), + String.format("%s OIDC Id Token", prefix), credentials.getOauth().getIdToken()); + } + break; } } } @@ -269,22 +285,24 @@ public void delete(final Host bookmark) throws LocalAccessDeniedException { protocol.getTokenPlaceholder() : String.format("%s (%s)", protocol.getTokenPlaceholder(), credentials.getUsername())); } if(protocol.isOAuthConfigurable()) { - final String prefix = this.getOAuthPrefix(bookmark); - if(StringUtils.isNotBlank(credentials.getOauth().getAccessToken())) { - this.deletePassword(protocol.getScheme(), this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), - String.format("%s OAuth2 Access Token", prefix)); - } - if(StringUtils.isNotBlank(credentials.getOauth().getRefreshToken())) { - this.deletePassword(protocol.getScheme(), this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), - String.format("%s OAuth2 Refresh Token", prefix)); - } - // Save expiry - if(credentials.getOauth().getExpiryInMilliseconds() != null) { - this.deletePassword(this.getOAuthHostname(bookmark), String.format("%s OAuth2 Token Expiry", prefix)); - } - if(StringUtils.isNotBlank(credentials.getOauth().getIdToken())) { - this.deletePassword(protocol.getScheme(), this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), - String.format("%s OIDC Id Token", prefix)); + final String[] descriptors = this.getOAuthPrefix(bookmark); + for(String prefix : descriptors) { + if(StringUtils.isNotBlank(credentials.getOauth().getAccessToken())) { + this.deletePassword(protocol.getScheme(), this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), + String.format("%s OAuth2 Access Token", prefix)); + } + if(StringUtils.isNotBlank(credentials.getOauth().getRefreshToken())) { + this.deletePassword(protocol.getScheme(), this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), + String.format("%s OAuth2 Refresh Token", prefix)); + } + // Save expiry + if(credentials.getOauth().getExpiryInMilliseconds() != null) { + this.deletePassword(this.getOAuthHostname(bookmark), String.format("%s OAuth2 Token Expiry", prefix)); + } + if(StringUtils.isNotBlank(credentials.getOauth().getIdToken())) { + this.deletePassword(protocol.getScheme(), this.getOAuthPort(bookmark), this.getOAuthHostname(bookmark), + String.format("%s OIDC Id Token", prefix)); + } } } }