diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java index cd58056a485b..41be2cf7884e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java @@ -52,7 +52,7 @@ public class AuthorizationCodeCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java index 44dfd883582c..e3929c58f0a1 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java @@ -25,8 +25,6 @@ */ @Immutable public class ClientCertificateCredential implements TokenCredential { - private final String clientCertificate; - private final String clientCertificatePassword; private final IdentityClient identityClient; /** @@ -40,22 +38,19 @@ public class ClientCertificateCredential implements TokenCredential { ClientCertificateCredential(String tenantId, String clientId, String certificatePath, String certificatePassword, IdentityClientOptions identityClientOptions) { Objects.requireNonNull(certificatePath, "'certificatePath' cannot be null."); - this.clientCertificate = certificatePath; - this.clientCertificatePassword = certificatePassword; - identityClient = - new IdentityClientBuilder() - .tenantId(tenantId) - .clientId(clientId) - .identityClientOptions(identityClientOptions) - .build(); + identityClient = new IdentityClientBuilder() + .tenantId(tenantId) + .clientId(clientId) + .certificatePath(certificatePath) + .certificatePassword(certificatePassword) + .identityClientOptions(identityClientOptions) + .build(); } @Override public Mono getToken(TokenRequestContext request) { - if (clientCertificatePassword != null) { - return identityClient.authenticateWithPfxCertificate(clientCertificate, clientCertificatePassword, request); - } else { - return identityClient.authenticateWithPemCertificate(clientCertificate, request); - } + return identityClient.authenticateWithConfidentialClientCache(request) + .onErrorResume(t -> Mono.empty()) + .switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithConfidentialClient(request))); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredentialBuilder.java index b49acb935439..d606909a3690 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredentialBuilder.java @@ -20,7 +20,7 @@ public class ClientCertificateCredentialBuilder extends AadCredentialBuilderBase * Sets the client certificate for authenticating to AAD. * * @param certificatePath the PEM file containing the certificate - * @return the ClientCertificateCredentialBuilder itself + * @return An updated instance of this builder. */ public ClientCertificateCredentialBuilder pemCertificate(String certificatePath) { this.clientCertificate = certificatePath; @@ -32,7 +32,7 @@ public ClientCertificateCredentialBuilder pemCertificate(String certificatePath) * * @param certificatePath the password protected PFX file containing the certificate * @param clientCertificatePassword the password protecting the PFX file - * @return the ClientCertificateCredentialBuilder itself + * @return An updated instance of this builder. */ public ClientCertificateCredentialBuilder pfxCertificate(String certificatePath, String clientCertificatePassword) { this.clientCertificate = certificatePath; @@ -40,6 +40,18 @@ public ClientCertificateCredentialBuilder pfxCertificate(String certificatePath, return this; } + /** + * Sets whether to enable using the shared token cache. This is disabled by default. + * + * @param enabled indicates whether to enable using the shared token cache. + * + * @return An updated instance of this builder. + */ + public ClientCertificateCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); + return this; + } + /** * Creates a new {@link ClientCertificateCredential} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java index 28efcb6a0749..c9ee963dd452 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java @@ -25,8 +25,6 @@ */ @Immutable public class ClientSecretCredential implements TokenCredential { - /* The client secret value. */ - private final String clientSecret; private final IdentityClient identityClient; /** @@ -44,13 +42,15 @@ public class ClientSecretCredential implements TokenCredential { identityClient = new IdentityClientBuilder() .tenantId(tenantId) .clientId(clientId) + .clientSecret(clientSecret) .identityClientOptions(identityClientOptions) .build(); - this.clientSecret = clientSecret; } @Override public Mono getToken(TokenRequestContext request) { - return identityClient.authenticateWithClientSecret(clientSecret, request); + return identityClient.authenticateWithConfidentialClientCache(request) + .onErrorResume(t -> Mono.empty()) + .switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithConfidentialClient(request))); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredentialBuilder.java index d8061398d5e7..a5382f140665 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredentialBuilder.java @@ -18,13 +18,25 @@ public class ClientSecretCredentialBuilder extends AadCredentialBuilderBase getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java index ea868138423e..ca8d88e1f376 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java @@ -75,7 +75,7 @@ class IntelliJCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java index 3b66f2555fec..55f7fdf52dd6 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java @@ -54,7 +54,7 @@ public class InteractiveBrowserCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index ecf37596b9e3..261bfb1cbdea 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -70,7 +70,7 @@ public class SharedTokenCacheCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java index 39f63b14913e..a0ed76fbb2c3 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java @@ -56,7 +56,7 @@ public class UsernamePasswordCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java index c1825bc19775..59e78fa713fb 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java @@ -67,7 +67,7 @@ class VisualStudioCodeCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index a9f08fddf782..9b2443aaebd7 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -30,12 +30,13 @@ import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.DeviceCodeFlowParameters; import com.microsoft.aad.msal4j.IAccount; +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.IClientCredential; import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.RefreshTokenParameters; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; -import reactor.core.Exceptions; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -57,6 +58,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.security.GeneralSecurityException; import java.time.Duration; import java.time.LocalDateTime; import java.time.OffsetDateTime; @@ -72,6 +74,7 @@ import java.util.Random; import java.util.Scanner; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -93,9 +96,13 @@ public class IdentityClient { private final ClientLogger logger = new ClientLogger(IdentityClient.class); private final IdentityClientOptions options; + private ConfidentialClientApplication confidentialClientApplication; private PublicClientApplication publicClientApplication; private final String tenantId; private final String clientId; + private final String clientSecret; + private final String certificatePath; + private final String certificatePassword; private HttpPipelineAdapter httpPipelineAdapter; /** @@ -103,9 +110,14 @@ public class IdentityClient { * * @param tenantId the tenant ID of the application. * @param clientId the client ID of the application. + * @param clientSecret the client secret of the application. + * @param certificatePath the path to the PKCS12 or PEM certificate of the application. + * @param certificatePassword the password protecting the PFX certificate. * @param options the options configuring the client. */ - IdentityClient(String tenantId, String clientId, IdentityClientOptions options) { + IdentityClient(String tenantId, String clientId, String clientSecret, + String certificatePath, String certificatePassword, + IdentityClientOptions options) { if (tenantId == null) { tenantId = "organizations"; } @@ -114,67 +126,115 @@ public class IdentityClient { } this.tenantId = tenantId; this.clientId = clientId; + this.clientSecret = clientSecret; + this.certificatePath = certificatePath; + this.certificatePassword = certificatePassword; this.options = options; } - private PublicClientApplication getPublicClientApplication(boolean sharedTokenCacheCredential) { - if (publicClientApplication != null) { - return publicClientApplication; + private ConfidentialClientApplication getConfidentialClientApplication() { + if (confidentialClientApplication != null) { + return confidentialClientApplication; } else if (clientId == null) { throw logger.logExceptionAsError(new IllegalArgumentException( "A non-null value for client ID must be provided for user authentication.")); - } else { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; - PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); + } + String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; + IClientCredential credential; + if (clientSecret != null) { + credential = ClientCredentialFactory.createFromSecret(clientSecret); + } else if (certificatePath != null) { try { - publicClientApplicationBuilder = publicClientApplicationBuilder.authority(authorityUrl); - } catch (MalformedURLException e) { - throw logger.logExceptionAsWarning(new IllegalStateException(e)); - } - - // If user supplies the pipeline, then it should override all other properties - // as they should directly be set on the pipeline. - HttpPipeline httpPipeline = options.getHttpPipeline(); - if (httpPipeline != null) { - httpPipelineAdapter = new HttpPipelineAdapter(httpPipeline); - publicClientApplicationBuilder.httpClient(httpPipelineAdapter); - } else { - // If http client is set on the credential, then it should override the proxy options if any configured. - HttpClient httpClient = options.getHttpClient(); - if (httpClient != null) { - httpPipelineAdapter = new HttpPipelineAdapter(setupPipeline(httpClient)); - publicClientApplicationBuilder.httpClient(httpPipelineAdapter); - } else if (options.getProxyOptions() != null) { - publicClientApplicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); + if (certificatePassword == null) { + byte[] pemCertificateBytes = Files.readAllBytes(Paths.get(certificatePath)); + credential = ClientCredentialFactory.createFromCertificate( + CertificateUtil.privateKeyFromPem(pemCertificateBytes), + CertificateUtil.publicKeyFromPem(pemCertificateBytes)); } else { - //Http Client is null, proxy options are not set, use the default client and build the pipeline. - httpPipelineAdapter = new HttpPipelineAdapter(setupPipeline(HttpClient.createDefault())); - publicClientApplicationBuilder.httpClient(httpPipelineAdapter); + credential = ClientCredentialFactory.createFromCertificate( + new FileInputStream(certificatePath), certificatePassword); } + } catch (IOException | GeneralSecurityException e) { + throw logger.logExceptionAsError(new RuntimeException( + "Failed to parse the certificate for the credential: " + e.getMessage(), e)); } + } else { + throw logger.logExceptionAsError( + new IllegalArgumentException("Must provide client secret or client certificate path")); + } + ConfidentialClientApplication.Builder applicationBuilder = + ConfidentialClientApplication.builder(clientId, credential); + try { + applicationBuilder = applicationBuilder.authority(authorityUrl); + } catch (MalformedURLException e) { + throw logger.logExceptionAsWarning(new IllegalStateException(e)); + } + + initializeHttpPipelineAdapter(); + if (httpPipelineAdapter != null) { + applicationBuilder.httpClient(httpPipelineAdapter); + } else { + applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); + } - if (options.getExecutorService() != null) { - publicClientApplicationBuilder.executorService(options.getExecutorService()); + if (options.getExecutorService() != null) { + applicationBuilder.executorService(options.getExecutorService()); + } + if (options.isSharedTokenCacheEnabled()) { + try { + applicationBuilder.setTokenCacheAccessAspect( + new PersistenceTokenCacheAccessAspect(options.getConfidentialClientPersistenceSettings())); + } catch (Throwable t) { + throw logger.logExceptionAsError(new ClientAuthenticationException( + "Shared token cache is unavailable in this environment.", null, t)); } - if (options.isSharedTokenCacheEnabled()) { - try { - publicClientApplicationBuilder.setTokenCacheAccessAspect( - new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); - } catch (Throwable t) { - String message = "Shared token cache is unavailable in this environment."; - if (sharedTokenCacheCredential) { - throw logger.logExceptionAsError(new CredentialUnavailableException(message, t)); - } else { - throw logger.logExceptionAsError(new ClientAuthenticationException(message, null, t)); - } + } + this.confidentialClientApplication = applicationBuilder.build(); + return this.confidentialClientApplication; + } + + private PublicClientApplication getPublicClientApplication(boolean sharedTokenCacheCredential) { + if (publicClientApplication != null) { + return publicClientApplication; + } else if (clientId == null) { + throw logger.logExceptionAsError(new IllegalArgumentException( + "A non-null value for client ID must be provided for user authentication.")); + } + String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; + PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); + try { + publicClientApplicationBuilder = publicClientApplicationBuilder.authority(authorityUrl); + } catch (MalformedURLException e) { + throw logger.logExceptionAsWarning(new IllegalStateException(e)); + } + + initializeHttpPipelineAdapter(); + if (httpPipelineAdapter != null) { + publicClientApplicationBuilder.httpClient(httpPipelineAdapter); + } else { + publicClientApplicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); + } + + if (options.getExecutorService() != null) { + publicClientApplicationBuilder.executorService(options.getExecutorService()); + } + if (options.isSharedTokenCacheEnabled()) { + try { + publicClientApplicationBuilder.setTokenCacheAccessAspect( + new PersistenceTokenCacheAccessAspect(options.getPublicClientPersistenceSettings())); + } catch (Throwable t) { + String message = "Shared token cache is unavailable in this environment."; + if (sharedTokenCacheCredential) { + throw logger.logExceptionAsError(new CredentialUnavailableException(message, t)); + } else { + throw logger.logExceptionAsError(new ClientAuthenticationException(message, null, t)); } } - this.publicClientApplication = publicClientApplicationBuilder.build(); - return this.publicClientApplication; } + this.publicClientApplication = publicClientApplicationBuilder.build(); + return this.publicClientApplication; } - public Mono authenticateWithIntelliJ(TokenRequestContext request) { try { IntelliJCacheAccessor cacheAccessor = new IntelliJCacheAccessor(options.getIntelliJKeePassDatabasePath()); @@ -337,36 +397,13 @@ public Mono authenticateWithAzureCli(TokenRequestContext request) { /** * Asynchronously acquire a token from Active Directory with a client secret. * - * @param clientSecret the client secret of the application * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateWithClientSecret(String clientSecret, TokenRequestContext request) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; - try { - ConfidentialClientApplication.Builder applicationBuilder = - ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromSecret(clientSecret)) - .authority(authorityUrl); - - // If http pipeline is available, then it should override the proxy options if any configured. - if (httpPipelineAdapter != null) { - applicationBuilder.httpClient(httpPipelineAdapter); - } else if (options.getProxyOptions() != null) { - applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); - } - - if (options.getExecutorService() != null) { - applicationBuilder.executorService(options.getExecutorService()); - } - - ConfidentialClientApplication application = applicationBuilder.build(); - return Mono.fromFuture(application.acquireToken( - ClientCredentialParameters.builder(new HashSet<>(request.getScopes())) - .build())) - .map(ar -> new MsalToken(ar, options)); - } catch (MalformedURLException e) { - return Mono.error(e); - } + public Mono authenticateWithConfidentialClient(TokenRequestContext request) { + return Mono.fromFuture(() -> getConfidentialClientApplication().acquireToken( + ClientCredentialParameters.builder(new HashSet<>(request.getScopes())).build())) + .map(ar -> new MsalToken(ar, options)); } private HttpPipeline setupPipeline(HttpClient httpClient) { @@ -380,78 +417,6 @@ private HttpPipeline setupPipeline(HttpClient httpClient) { .policies(policies.toArray(new HttpPipelinePolicy[0])).build(); } - /** - * Asynchronously acquire a token from Active Directory with a PKCS12 certificate. - * - * @param pfxCertificatePath the path to the PKCS12 certificate of the application - * @param pfxCertificatePassword the password protecting the PFX certificate - * @param request the details of the token request - * @return a Publisher that emits an AccessToken - */ - public Mono authenticateWithPfxCertificate(String pfxCertificatePath, String pfxCertificatePassword, - TokenRequestContext request) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; - return Mono.fromCallable(() -> { - ConfidentialClientApplication.Builder applicationBuilder = - ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromCertificate( - new FileInputStream(pfxCertificatePath), pfxCertificatePassword)) - .authority(authorityUrl); - - // If http pipeline is available, then it should override the proxy options if any configured. - if (httpPipelineAdapter != null) { - applicationBuilder.httpClient(httpPipelineAdapter); - } else if (options.getProxyOptions() != null) { - applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); - } - - if (options.getExecutorService() != null) { - applicationBuilder.executorService(options.getExecutorService()); - } - - return applicationBuilder.build(); - }).flatMap(application -> Mono.fromFuture(application.acquireToken( - ClientCredentialParameters.builder(new HashSet<>(request.getScopes())).build()))) - .map(ar -> new MsalToken(ar, options)); - } - - /** - * Asynchronously acquire a token from Active Directory with a PEM certificate. - * - * @param pemCertificatePath the path to the PEM certificate of the application - * @param request the details of the token request - * @return a Publisher that emits an AccessToken - */ - public Mono authenticateWithPemCertificate(String pemCertificatePath, TokenRequestContext request) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; - try { - byte[] pemCertificateBytes = Files.readAllBytes(Paths.get(pemCertificatePath)); - ConfidentialClientApplication.Builder applicationBuilder = - ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromCertificate( - CertificateUtil.privateKeyFromPem(pemCertificateBytes), - CertificateUtil.publicKeyFromPem(pemCertificateBytes))) - .authority(authorityUrl); - - // If http pipeline is available, then it should override the proxy options if any configured. - if (httpPipelineAdapter != null) { - applicationBuilder.httpClient(httpPipelineAdapter); - } else if (options.getProxyOptions() != null) { - applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); - } - - if (options.getExecutorService() != null) { - applicationBuilder.executorService(options.getExecutorService()); - } - - ConfidentialClientApplication application = applicationBuilder.build(); - return Mono.fromFuture(application.acquireToken( - ClientCredentialParameters.builder(new HashSet<>(request.getScopes())) - .build())) - .map(ar -> new MsalToken(ar, options)); - } catch (IOException e) { - return Mono.error(e); - } - } - /** * Asynchronously acquire a token from Active Directory with a username and a password. * @@ -476,8 +441,8 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ * @param account the account used to login to acquire the last token * @return a Publisher that emits an AccessToken */ - public Mono authenticateWithMsalAccount(TokenRequestContext request, IAccount account) { - return Mono.defer(() -> Mono.fromFuture(() -> { + public Mono authenticateWithPublicClientCache(TokenRequestContext request, IAccount account) { + return Mono.fromFuture(() -> { SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( new HashSet<>(request.getScopes())); if (account != null) { @@ -487,22 +452,41 @@ public Mono authenticateWithMsalAccount(TokenRequestContext request, return getPublicClientApplication(false) .acquireTokenSilently(parametersBuilder.build()); } catch (MalformedURLException e) { - throw logger.logExceptionAsError(Exceptions.propagate(e)); + return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); } }).map(ar -> new MsalToken(ar, options)) - .filter(t -> !t.isExpired()) - .switchIfEmpty(Mono.fromFuture(() -> { - SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( - new HashSet<>(request.getScopes())).forceRefresh(true); - if (account != null) { - forceParametersBuilder = forceParametersBuilder.account(account); - } + .filter(t -> !t.isExpired()) + .switchIfEmpty(Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())).forceRefresh(true); + if (account != null) { + forceParametersBuilder = forceParametersBuilder.account(account); + } + try { + return getPublicClientApplication(false).acquireTokenSilently(forceParametersBuilder.build()); + } catch (MalformedURLException e) { + return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); + } + }).map(result -> new MsalToken(result, options))); + } + + /** + * Asynchronously acquire a token from the currently logged in client. + * + * @param request the details of the token request + * @return a Publisher that emits an AccessToken + */ + public Mono authenticateWithConfidentialClientCache(TokenRequestContext request) { + return Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())); try { - return getPublicClientApplication(false).acquireTokenSilently(forceParametersBuilder.build()); + return getConfidentialClientApplication().acquireTokenSilently(parametersBuilder.build()); } catch (MalformedURLException e) { - throw logger.logExceptionAsError(Exceptions.propagate(e)); + return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); } - }).map(result -> new MsalToken(result, options)))); + }).map(ar -> (AccessToken) new MsalToken(ar, options)) + .filter(t -> !t.isExpired()); } /** @@ -653,7 +637,7 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext requ } - return authenticateWithMsalAccount(request, requestedAccount); + return authenticateWithPublicClientCache(request, requestedAccount); }); } @@ -877,4 +861,28 @@ void openUrl(String url) throws IOException { logger.error("Browser could not be opened - please open {} in a browser on this device.", url); } } + + private CompletableFuture getFailedCompletableFuture(Exception e) { + CompletableFuture completableFuture = new CompletableFuture<>(); + completableFuture.completeExceptionally(e); + return completableFuture; + } + + private void initializeHttpPipelineAdapter() { + // If user supplies the pipeline, then it should override all other properties + // as they should directly be set on the pipeline. + HttpPipeline httpPipeline = options.getHttpPipeline(); + if (httpPipeline != null) { + httpPipelineAdapter = new HttpPipelineAdapter(httpPipeline); + } else { + // If http client is set on the credential, then it should override the proxy options if any configured. + HttpClient httpClient = options.getHttpClient(); + if (httpClient != null) { + httpPipelineAdapter = new HttpPipelineAdapter(setupPipeline(httpClient)); + } else if (options.getProxyOptions() == null) { + //Http Client is null, proxy options are not set, use the default client and build the pipeline. + httpPipelineAdapter = new HttpPipelineAdapter(setupPipeline(HttpClient.createDefault())); + } + } + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java index 9bc41c4a6801..a25acfdf8adf 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java @@ -12,6 +12,9 @@ public final class IdentityClientBuilder { private IdentityClientOptions identityClientOptions; private String tenantId; private String clientId; + private String clientSecret; + private String certificatePath; + private String certificatePassword; /** * Sets the tenant ID for the client. @@ -33,6 +36,39 @@ public IdentityClientBuilder clientId(String clientId) { return this; } + /** + * Sets the client secret for the client. + * @param clientSecret the secret value of the AAD application. + * @return the IdentityClientBuilder itself + */ + public IdentityClientBuilder clientSecret(String clientSecret) { + this.clientSecret = clientSecret; + return this; + } + + /** + * Sets the client certificate for the client. + * + * @param certificatePath the PEM/PFX file containing the certificate + * @return the IdentityClientBuilder itself + */ + public IdentityClientBuilder certificatePath(String certificatePath) { + this.certificatePath = certificatePath; + return this; + } + + /** + * Sets the client certificate for the client. + * + * @param certificatePassword the password protecting the PFX file + * @return the IdentityClientBuilder itself + */ + public IdentityClientBuilder certificatePassword(String certificatePassword) { + this.certificatePassword = certificatePassword; + return this; + } + + /** * Sets the options for the client. * @param identityClientOptions the options for the client. @@ -47,6 +83,7 @@ public IdentityClientBuilder identityClientOptions(IdentityClientOptions identit * @return a {@link IdentityClient} with the current configurations. */ public IdentityClient build() { - return new IdentityClient(tenantId, clientId, identityClientOptions); + return new IdentityClient(tenantId, clientId, clientSecret, certificatePath, + certificatePassword, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 26e161fc3721..78c23ecddd4a 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -24,15 +24,18 @@ */ public final class IdentityClientOptions { private static final int MAX_RETRY_DEFAULT_LIMIT = 3; - private static final String DEFAULT_CACHE_FILE_NAME = "msal.cache"; + private static final String DEFAULT_PUBLIC_CACHE_FILE_NAME = "msal.cache"; + private static final String DEFAULT_CONFIDENTIAL_CACHE_FILE_NAME = "msal.confidential.cache"; private static final Path DEFAULT_CACHE_FILE_PATH = Platform.isWindows() ? Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService") : Paths.get(System.getProperty("user.home"), ".IdentityService"); private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; - private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; + private static final String DEFAULT_PUBLIC_KEYCHAIN_ACCOUNT = "MSALCache"; + private static final String DEFAULT_CONFIDENTIAL_KEYCHAIN_ACCOUNT = "MSALConfidentialCache"; private static final String DEFAULT_KEYRING_NAME = "default"; private static final String DEFAULT_KEYRING_SCHEMA = "msal.cache"; - private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; + private static final String DEFAULT_PUBLIC_KEYRING_ITEM_NAME = DEFAULT_PUBLIC_KEYCHAIN_ACCOUNT; + private static final String DEFAULT_CONFIDENTIAL_KEYRING_ITEM_NAME = DEFAULT_CONFIDENTIAL_KEYCHAIN_ACCOUNT; private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; @@ -44,14 +47,6 @@ public final class IdentityClientOptions { private ExecutorService executorService; private Duration tokenRefreshOffset = Duration.ofMinutes(2); private HttpClient httpClient; - private Path cacheFileDirectory; - private String cacheFileName; - private String keychainService; - private String keychainAccount; - private String keyringName; - private String keyringItemSchema; - private String keyringItemName; - private final String[] attributes; // preserve order private boolean allowUnencryptedCache; private boolean sharedTokenCacheEnabled; private String keePassDatabasePath; @@ -64,14 +59,6 @@ public IdentityClientOptions() { authorityHost = configuration.get(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, KnownAuthorityHosts.AZURE_CLOUD); maxRetry = MAX_RETRY_DEFAULT_LIMIT; retryTimeout = i -> Duration.ofSeconds((long) Math.pow(2, i.getSeconds() - 1)); - cacheFileDirectory = DEFAULT_CACHE_FILE_PATH; - cacheFileName = DEFAULT_CACHE_FILE_NAME; - keychainService = DEFAULT_KEYCHAIN_SERVICE; - keychainAccount = DEFAULT_KEYCHAIN_ACCOUNT; - keyringName = DEFAULT_KEYRING_NAME; - keyringItemSchema = DEFAULT_KEYRING_SCHEMA; - keyringItemName = DEFAULT_KEYRING_ITEM_NAME; - attributes = new String[] { DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE }; allowUnencryptedCache = false; sharedTokenCacheEnabled = false; } @@ -231,13 +218,22 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { return this; } - PersistenceSettings getPersistenceSettings() { - return PersistenceSettings.builder(cacheFileName, cacheFileDirectory) - .setMacKeychain(keychainService, keychainAccount) - .setLinuxKeyring(keyringName, keyringItemSchema, keyringItemName, - attributes[0], attributes[1], null, null) - .setLinuxUseUnprotectedFileAsCacheStorage(allowUnencryptedCache) - .build(); + PersistenceSettings getPublicClientPersistenceSettings() { + return PersistenceSettings.builder(DEFAULT_PUBLIC_CACHE_FILE_NAME, DEFAULT_CACHE_FILE_PATH) + .setMacKeychain(DEFAULT_KEYCHAIN_SERVICE, DEFAULT_PUBLIC_KEYCHAIN_ACCOUNT) + .setLinuxKeyring(DEFAULT_KEYRING_NAME, DEFAULT_KEYRING_SCHEMA, DEFAULT_PUBLIC_KEYRING_ITEM_NAME, + DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE, null, null) + .setLinuxUseUnprotectedFileAsCacheStorage(allowUnencryptedCache) + .build(); + } + + PersistenceSettings getConfidentialClientPersistenceSettings() { + return PersistenceSettings.builder(DEFAULT_CONFIDENTIAL_CACHE_FILE_NAME, DEFAULT_CACHE_FILE_PATH) + .setMacKeychain(DEFAULT_KEYCHAIN_SERVICE, DEFAULT_CONFIDENTIAL_KEYCHAIN_ACCOUNT) + .setLinuxKeyring(DEFAULT_KEYRING_NAME, DEFAULT_KEYRING_SCHEMA, DEFAULT_CONFIDENTIAL_KEYRING_ITEM_NAME, + DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE, null, null) + .setLinuxUseUnprotectedFileAsCacheStorage(allowUnencryptedCache) + .build(); } /** diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java index b15a28f51478..511f9705dc27 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java @@ -47,7 +47,7 @@ public void testValidAuthorizationCode() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithAuthorizationCode(eq(request1), eq(authCode1), eq(redirectUri))) .thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithMsalAccount(any(), any())) + when(identityClient.authenticateWithPublicClientCache(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java index 9ab7dcfe867d..c2e88775cbd1 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java @@ -22,6 +22,9 @@ import java.util.UUID; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @@ -45,10 +48,14 @@ public void testValidCertificates() throws Exception { OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); // mock - IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithPemCertificate(pemPath, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); - when(identityClient.authenticateWithPfxCertificate(pfxPath, pfxPassword, request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt)); - PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + IdentityClient pemIdentityClient = PowerMockito.mock(IdentityClient.class); + IdentityClient pfxIdentityClient = PowerMockito.mock(IdentityClient.class); + when(pemIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(pfxIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(pemIdentityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(pfxIdentityClient.authenticateWithConfidentialClient(request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt)); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), isNull(), eq(pemPath), isNull(), any()).thenReturn(pemIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), isNull(), eq(pfxPath), eq(pfxPassword), any()).thenReturn(pfxIdentityClient); // test ClientCertificateCredential credential = @@ -76,10 +83,14 @@ public void testInvalidCertificates() throws Exception { OffsetDateTime expiresOn = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); // mock - IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithPemCertificate(pemPath, request1)).thenReturn(Mono.error(new MsalServiceException("bad pem", "BadPem"))); - when(identityClient.authenticateWithPfxCertificate(pfxPath, pfxPassword, request2)).thenReturn(Mono.error(new MsalServiceException("bad pfx", "BadPfx"))); - PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + IdentityClient pemIdentityClient = PowerMockito.mock(IdentityClient.class); + IdentityClient pfxIdentityClient = PowerMockito.mock(IdentityClient.class); + when(pemIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(pfxIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(pemIdentityClient.authenticateWithConfidentialClient(request1)).thenReturn(Mono.error(new MsalServiceException("bad pem", "BadPem"))); + when(pfxIdentityClient.authenticateWithConfidentialClient(request2)).thenReturn(Mono.error(new MsalServiceException("bad pfx", "BadPfx"))); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), isNull(), eq(pemPath), isNull(), any()).thenReturn(pemIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), isNull(), eq(pfxPath), eq(pfxPassword), any()).thenReturn(pfxIdentityClient); // test ClientCertificateCredential credential = @@ -105,8 +116,9 @@ public void testInvalidParameters() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithPemCertificate(pemPath, request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); - PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(identityClient.authenticateWithConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), isNull(), eq(pemPath), isNull(), any()).thenReturn(identityClient); // test try { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java index 3cb4359cb004..cc692863a865 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java @@ -23,6 +23,9 @@ import java.util.UUID; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @@ -45,8 +48,9 @@ public void testValidSecrets() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithClientSecret(secret, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); - when(identityClient.authenticateWithClientSecret(secret, request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt)); + when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(identityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateWithConfidentialClient(request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt)); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test @@ -75,8 +79,9 @@ public void testValidSecretsWithTokenRefreshOffset() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithClientSecret(secret, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt, offset)); - when(identityClient.authenticateWithClientSecret(secret, request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt, offset)); + when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(identityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt, offset)); + when(identityClient.authenticateWithConfidentialClient(request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt, offset)); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test @@ -107,9 +112,13 @@ public void testInvalidSecrets() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithClientSecret(secret, request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); - when(identityClient.authenticateWithClientSecret(badSecret, request)).thenReturn(Mono.error(new MsalServiceException("bad secret", "BadSecret"))); - PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + IdentityClient badIdentityClient = PowerMockito.mock(IdentityClient.class); + when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(badIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(identityClient.authenticateWithConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); + when(badIdentityClient.authenticateWithConfidentialClient(request)).thenReturn(Mono.error(new MsalServiceException("bad secret", "BadSecret"))); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), eq(secret), isNull(), isNull(), any()).thenReturn(identityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), eq(badSecret), isNull(), isNull(), any()).thenReturn(badIdentityClient); // test ClientSecretCredential credential = @@ -135,7 +144,8 @@ public void testInvalidParameters() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithClientSecret(secret, request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); + when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(identityClient.authenticateWithConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java index a01add160fb3..f57efbe5be80 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java @@ -21,6 +21,8 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @@ -48,8 +50,9 @@ public void testUseEnvironmentCredential() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateWithClientSecret(secret, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); - PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); + when(identityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(tenantId), eq(clientId), eq(secret), isNull(), isNull(), any()).thenReturn(identityClient); IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class); when(intelliJCredential.getToken(request1)) diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java index 8660390ff28d..adca2257f306 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java @@ -46,7 +46,7 @@ public void testValidDeviceCode() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithDeviceCode(eq(request1), eq(consumer))).thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithMsalAccount(any(), any())) + when(identityClient.authenticateWithPublicClientCache(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java index 1b5e1e2a94a8..7145a5086b4f 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java @@ -48,7 +48,7 @@ public void testValidInteractive() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithBrowserInteraction(eq(request1), eq(port))).thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithMsalAccount(any(), any())) + when(identityClient.authenticateWithPublicClientCache(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java index bd75c2307e03..73c27fd63708 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java @@ -47,7 +47,7 @@ public void testValidUserCredential() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithUsernamePassword(request1, username, password)).thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithMsalAccount(any(), any())) + when(identityClient.authenticateWithPublicClientCache(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { @@ -83,7 +83,7 @@ public void testInvalidUserCredential() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithUsernamePassword(request, username, badPassword)).thenThrow(new MsalServiceException("bad credential", "BadCredential")); - when(identityClient.authenticateWithMsalAccount(any(), any())) + when(identityClient.authenticateWithPublicClientCache(any(), any())) .thenAnswer(invocation -> Mono.error(new UnsupportedOperationException("nothing cached"))); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); @@ -107,7 +107,7 @@ public void testInvalidParameters() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithUsernamePassword(request, username, password)).thenReturn(TestUtils.getMockMsalToken(token1, expiresOn)); - when(identityClient.authenticateWithMsalAccount(any(), any())) + when(identityClient.authenticateWithPublicClientCache(any(), any())) .thenAnswer(invocation -> Mono.error(new UnsupportedOperationException("nothing cached"))); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java index 72adcc2c2f8d..e049c1f4e9bb 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java @@ -23,13 +23,13 @@ public class IdentityClientIntegrationTests { @Ignore("Integration tests") public void clientSecretCanGetToken() { - IdentityClient client = new IdentityClient(System.getenv(AZURE_TENANT_ID), System.getenv(AZURE_CLIENT_ID), new IdentityClientOptions()); - StepVerifier.create(client.authenticateWithClientSecret(System.getenv(AZURE_CLIENT_SECRET), request)) + IdentityClient client = new IdentityClient(System.getenv(AZURE_TENANT_ID), System.getenv(AZURE_CLIENT_ID), System.getenv(AZURE_CLIENT_SECRET), null, null, new IdentityClientOptions()); + StepVerifier.create(client.authenticateWithConfidentialClient(request)) .expectNextMatches(token -> token.getToken() != null && token.getExpiresAt() != null && !token.isExpired()) .verifyComplete(); - StepVerifier.create(client.authenticateWithClientSecret(System.getenv(AZURE_CLIENT_SECRET), new TokenRequestContext().addScopes("https://vault.azure.net/.default"))) + StepVerifier.create(client.authenticateWithConfidentialClient(new TokenRequestContext().addScopes("https://vault.azure.net/.default"))) .expectNextMatches(token -> token.getToken() != null && token.getExpiresAt() != null && !token.isExpired()) @@ -38,7 +38,7 @@ public void clientSecretCanGetToken() { @Ignore("Integration tests") public void deviceCodeCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithDeviceCode(request, deviceCode -> { System.out.println(deviceCode.getMessage()); try { @@ -51,7 +51,7 @@ public void deviceCodeCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); + token = client.authenticateWithPublicClientCache(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -60,13 +60,13 @@ public void deviceCodeCanGetToken() { @Ignore("Integration tests") public void browserCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithBrowserInteraction(request, 8765).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); + token = client.authenticateWithPublicClientCache(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -75,13 +75,13 @@ public void browserCanGetToken() { @Ignore("Integration tests") public void usernamePasswordCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithUsernamePassword(request, System.getenv("username"), System.getenv("password")).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); + token = client.authenticateWithPublicClientCache(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -90,13 +90,13 @@ public void usernamePasswordCanGetToken() { @Ignore("Integration tests") public void authCodeCanGetToken() throws Exception { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), new IdentityClientOptions()); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, new IdentityClientOptions()); MsalToken token = client.authenticateWithAuthorizationCode(request, System.getenv("AZURE_AUTH_CODE"), new URI("http://localhost:8000")).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); + token = client.authenticateWithPublicClientCache(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index 72fe05076ecc..afec805e3c62 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -72,8 +72,9 @@ public void testValidSecret() throws Exception { mockForClientSecret(secret, request, accessToken, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); - AccessToken token = client.authenticateWithClientSecret(secret, request).block(); + IdentityClient client = new IdentityClientBuilder() + .tenantId(tenantId).clientId(clientId).clientSecret(secret).build(); + AccessToken token = client.authenticateWithConfidentialClient(request).block(); Assert.assertEquals(accessToken, token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); } @@ -91,8 +92,9 @@ public void testInvalidSecret() throws Exception { // test try { - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); - client.authenticateWithClientSecret("bad secret", request).block(); + IdentityClient client = new IdentityClientBuilder() + .tenantId(tenantId).clientId(clientId).clientSecret("bad secret").build(); + client.authenticateWithConfidentialClient(request).block(); fail(); } catch (MsalServiceException e) { Assert.assertEquals("Invalid clientSecret", e.getMessage()); @@ -111,8 +113,9 @@ public void testValidCertificate() throws Exception { mockForClientCertificate(request, accessToken, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); - AccessToken token = client.authenticateWithPfxCertificate(pfxPath, "StrongPass!123", request).block(); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId) + .certificatePath(pfxPath).certificatePassword("StrongPass!123").build(); + AccessToken token = client.authenticateWithConfidentialClient(request).block(); Assert.assertEquals(accessToken, token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); } @@ -131,8 +134,9 @@ public void testPemCertificate() throws Exception { // mock mockForClientPemCertificate(accessToken, request, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); - AccessToken token = client.authenticateWithPemCertificate(pemPath, request).block(); + IdentityClient client = new IdentityClientBuilder() + .tenantId(tenantId).clientId(clientId).certificatePath(pemPath).build(); + AccessToken token = client.authenticateWithConfidentialClient(request).block(); Assert.assertEquals(accessToken, token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); } @@ -150,8 +154,9 @@ public void testInvalidCertificatePassword() throws Exception { // test try { - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); - client.authenticateWithPfxCertificate(pfxPath, "BadPassword", request).block(); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId) + .certificatePath(pfxPath).certificatePassword("BadPassword").build(); + client.authenticateWithConfidentialClient(request).block(); fail(); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("password was incorrect")); @@ -260,7 +265,7 @@ public void testUserRefreshTokenflow() throws Exception { // test IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); - StepVerifier.create(client.authenticateWithMsalAccount(request2, TestUtils.getMockMsalAccount(token1, expiresAt).block())) + StepVerifier.create(client.authenticateWithPublicClientCache(request2, TestUtils.getMockMsalAccount(token1, expiresAt).block())) .expectNextMatches(accessToken -> token2.equals(accessToken.getToken()) && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) .verifyComplete(); @@ -320,7 +325,7 @@ public void testOpenUrl() throws Exception { when(rt.exec(anyString())).thenReturn(a); // test - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClient client = new IdentityClientBuilder().clientId("dummy").build(); client.openUrl("https://localhost.com"); }