diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index 4b770581f2023..eeb5e4a6adfdb 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -22,6 +22,7 @@ com.azure:azure-cosmos-benchmark;4.0.1-beta.1;4.0.1-beta.1 com.azure:azure-data-appconfiguration;1.1.1;1.2.0-beta.1 com.azure:azure-e2e;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-identity;1.0.5;1.1.0-beta.4 +com.azure:azure-identity-perf;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-messaging-eventhubs;5.0.3;5.1.0-beta.1 com.azure:azure-messaging-eventhubs-checkpointstore-blob;1.0.3;1.1.0-beta.1 com.azure:azure-messaging-servicebus;7.0.0-beta.1;7.0.0-beta.2 diff --git a/sdk/identity/azure-identity-perf/README.md b/sdk/identity/azure-identity-perf/README.md new file mode 100644 index 0000000000000..73530b2e3b06a --- /dev/null +++ b/sdk/identity/azure-identity-perf/README.md @@ -0,0 +1,32 @@ +# Azure Identity Performance test client library for Java + +Represents Performance tests for Azure Identity SDK for Java. + +## Getting started + +### Prerequisites + +- Java Development Kit (JDK) with version 8 or above + +### Adding the package to your product + + +## Key concepts + + +## Examples + +## Troubleshooting + +## Next steps + +## Contributing + +If you would like to become an active contributor to this project please follow the instructions provided in [Microsoft +Azure Projects Contribution Guidelines](http://azure.github.io/guidelines.html). + +1. Fork it +1. Create your feature branch (`git checkout -b my-new-feature`) +1. Commit your changes (`git commit -am 'Add some feature'`) +1. Push to the branch (`git push origin my-new-feature`) +1. Create new Pull Request diff --git a/sdk/identity/azure-identity-perf/pom.xml b/sdk/identity/azure-identity-perf/pom.xml new file mode 100644 index 0000000000000..a48d02f94172e --- /dev/null +++ b/sdk/identity/azure-identity-perf/pom.xml @@ -0,0 +1,68 @@ + + + + + + com.azure + azure-client-sdk-parent + 1.7.0 + ../../../pom.client.xml + + + 4.0.0 + + com.azure + azure-identity-perf + 1.0.0-beta.1 + jar + + + UTF-8 + 1.8 + 1.8 + + + + + com.azure + azure-identity + 1.1.0-beta.4 + + + com.azure + perf-test-core + 1.0.0-beta.1 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.2.0 + + + package + + single + + + + + + com.azure.identity.perf.App + + + + + jar-with-dependencies + + + + + + + + diff --git a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/App.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/App.java new file mode 100644 index 0000000000000..93f28cb15b7bd --- /dev/null +++ b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/App.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf; + +import com.azure.perf.test.core.PerfStressProgram; + +/** + * Runs the Identity performance tests. + * + * Test scenarios: + * 1. Read cache from a single process + * 2. Read cache from multiple processes + * 3. Write cache from a single process + * 4. Write cache from multiple processes + * + *

To run from command line. Package the project into a jar with dependencies via mvn clean package. + * Then run the program via java -jar 'compiled-jar-with-dependencies-path'

+ * + *

To run from IDE, set all the required environment variables in IntelliJ via Run -> EditConfigurations section. + * Then run the App's main method via IDE.

+ */ +public class App { + public static void main(String[] args) { + Class[] testClasses; + + try { + testClasses = new Class[] { + Class.forName("com.azure.identity.perf.ReadCache"), + Class.forName("com.azure.identity.perf.WriteCache"), + }; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + PerfStressProgram.run(testClasses, args); + } +} diff --git a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java new file mode 100644 index 0000000000000..e3a87ea9fe685 --- /dev/null +++ b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf; + +import com.azure.identity.SharedTokenCacheCredential; +import com.azure.identity.SharedTokenCacheCredentialBuilder; +import com.azure.identity.perf.core.ServiceTest; +import com.azure.perf.test.core.PerfStressOptions; +import reactor.core.publisher.Mono; + +public class ReadCache extends ServiceTest { + private final SharedTokenCacheCredential credential; + + public ReadCache(PerfStressOptions options) { + super(options); + credential = new SharedTokenCacheCredentialBuilder() + .clientId(CLI_CLIENT_ID) + .build(); + } + + // Perform the API call to be tested here + @Override + public void run() { + credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).block(); + } + + @Override + public Mono runAsync() { + return credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).then(); + } +} diff --git a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/WriteCache.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/WriteCache.java new file mode 100644 index 0000000000000..edee2684493f2 --- /dev/null +++ b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/WriteCache.java @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf; + +import com.azure.identity.SharedTokenCacheCredential; +import com.azure.identity.SharedTokenCacheCredentialBuilder; +import com.azure.identity.perf.core.ServiceTest; +import com.azure.perf.test.core.PerfStressOptions; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +public class WriteCache extends ServiceTest { + private final SharedTokenCacheCredential credential; + + public WriteCache(PerfStressOptions options) { + super(options); + credential = new SharedTokenCacheCredentialBuilder() + .clientId(CLI_CLIENT_ID) + .tokenRefreshOffset(Duration.ofMinutes(60)) + .build(); + } + + // Perform the API call to be tested here + @Override + public void run() { + credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).block(); + } + + @Override + public Mono runAsync() { + return credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).then(); + } +} diff --git a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java new file mode 100644 index 0000000000000..6b2ac25f18e4c --- /dev/null +++ b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf.core; + +import com.azure.core.credential.TokenRequestContext; +import com.azure.identity.InteractiveBrowserCredential; +import com.azure.identity.InteractiveBrowserCredentialBuilder; +import com.azure.perf.test.core.PerfStressOptions; +import com.azure.perf.test.core.PerfStressTest; +import reactor.core.publisher.Mono; + +public abstract class ServiceTest extends PerfStressTest { + protected static final String CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + protected static final TokenRequestContext ARM_TOKEN_REQUEST_CONTEXT = new TokenRequestContext() + .addScopes("https://management.azure.com/.default"); + + private InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder() + .port(8765) + .clientId(CLI_CLIENT_ID) + .build(); + + public ServiceTest(TOptions options) { + super(options); + } + + @Override + public Mono globalSetupAsync() { + // Populate the token cache for tests + return super.globalSetupAsync() + .then(interactiveBrowserCredential.getToken(ARM_TOKEN_REQUEST_CONTEXT)) + .then(); + } +} diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml index cd7341c42a581..274031589e10b 100644 --- a/sdk/identity/azure-identity/pom.xml +++ b/sdk/identity/azure-identity/pom.xml @@ -34,6 +34,11 @@ msal4j 1.3.0 + + com.microsoft.azure + msal4j-persistence-extension + 0.1 + com.nimbusds oauth2-oidc-sdk 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 e3a039ce50151..cd58056a485b2 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.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(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/AuthorizationCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java index c9a00e6c9c4d1..c92e63932c074 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java @@ -47,6 +47,31 @@ public AuthorizationCodeCredentialBuilder redirectUrl(String redirectUrl) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public AuthorizationCodeCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. This is disabled by default. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public AuthorizationCodeCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); + return this; + } + /** * Creates a new {@link AuthorizationCodeCredential} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java index dbd820196392f..4e2648ee77e67 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java @@ -48,7 +48,7 @@ public class DeviceCodeCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(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/DeviceCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java index 08d74b043a1ab..0c6836f0c8851 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java @@ -29,6 +29,31 @@ public DeviceCodeCredentialBuilder challengeConsumer( return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public DeviceCodeCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. This is disabled by default. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public DeviceCodeCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); + return this; + } + /** * Creates a new {@link DeviceCodeCredential} with the current configurations. * 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 ab9baa21176d5..3798a75f793ad 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 @@ -72,7 +72,7 @@ class IntelliJCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(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 28aa716c9de0c..3b66f2555feca 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.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(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/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index d0ef8e3a863f3..72f78d7f356f9 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -27,6 +27,31 @@ public InteractiveBrowserCredentialBuilder port(int port) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public InteractiveBrowserCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. This is disabled by default. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public InteractiveBrowserCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); + return this; + } + /** * Creates a new {@link InteractiveBrowserCredential} with the current configurations. * 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 8565844826b30..ecf37596b9e31 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 @@ -7,21 +7,13 @@ import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.Configuration; -import com.azure.core.util.CoreUtils; +import com.azure.identity.implementation.IdentityClient; +import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; -import com.azure.identity.implementation.msalextensions.PersistentTokenCacheAccessAspect; -import com.microsoft.aad.msal4j.IAccount; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import com.microsoft.aad.msal4j.PublicClientApplication; -import com.microsoft.aad.msal4j.SilentParameters; +import com.azure.identity.implementation.MsalToken; import reactor.core.publisher.Mono; -import java.net.MalformedURLException; -import java.time.ZoneOffset; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; /** * A credential provider that provides token credentials from the MSAL shared token cache. @@ -32,9 +24,9 @@ public class SharedTokenCacheCredential implements TokenCredential { private final String username; private final String clientId; private final String tenantId; - private final IdentityClientOptions options; + private final AtomicReference cachedToken; - private PublicClientApplication pubClient = null; + private final IdentityClient identityClient; /** * Creates an instance of the Shared Token Cache Credential Provider. @@ -63,7 +55,12 @@ public class SharedTokenCacheCredential implements TokenCredential { } else { this.tenantId = tenantId; } - this.options = identityClientOptions; + this.identityClient = new IdentityClientBuilder() + .tenantId(this.tenantId) + .clientId(this.clientId) + .identityClientOptions(identityClientOptions) + .build(); + this.cachedToken = new AtomicReference<>(); } /** @@ -71,84 +68,18 @@ public class SharedTokenCacheCredential implements TokenCredential { * */ @Override public Mono getToken(TokenRequestContext request) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; - // Initialize here so that the constructor doesn't throw - if (pubClient == null) { - try { - PersistentTokenCacheAccessAspect accessAspect = new PersistentTokenCacheAccessAspect(); - PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); - if (options.getExecutorService() != null) { - applicationBuilder.executorService(options.getExecutorService()); - } - - pubClient = applicationBuilder - .authority(authorityUrl) - .setTokenCacheAccessAspect(accessAspect) - .build(); - } catch (Exception e) { - return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential authentication " - + "unavailable." + e.getMessage(), e)); + return Mono.defer(() -> { + if (cachedToken.get() != null) { + return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + .onErrorResume(t -> Mono.empty()); + } else { + return Mono.empty(); } - } - - // find if the Public Client app with the requested username exists - return Mono.fromFuture(pubClient.getAccounts()) - .flatMap(set -> { - IAccount requestedAccount; - Map accounts = new HashMap<>(); // home account id -> account - - for (IAccount cached : set) { - if (username == null || username.equals(cached.username())) { - if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one - accounts.put(cached.homeAccountId(), cached); - } - } - } - - if (set.size() == 0) { - return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential authentication " - + "unavailable. No accounts were found in the cache.")); - } - - if (CoreUtils.isNullOrEmpty(username)) { - return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential authentication " - + "unavailable. Multiple accounts were found in the cache. Use username and tenant id " - + "to disambiguate.")); - } - - if (accounts.size() != 1) { - if (accounts.size() == 0) { - return Mono.error(new CredentialUnavailableException( - String.format("SharedTokenCacheCredential authentication " - + "unavailable. No account matching the specified username %s was found in " - + "the cache.", username))); - } else { - return Mono.error(new CredentialUnavailableException(String.format("SharedTokenCacheCredential" - + " authentication unavailable. Multiple accounts matching the specified username %s were " - + "found in the cache.", username))); - } - } - - requestedAccount = accounts.values().iterator().next(); - - - // if it does, then request the token - SilentParameters params = SilentParameters.builder( - new HashSet<>(request.getScopes()), requestedAccount) - .authorityUrl(authorityUrl) - .build(); - - CompletableFuture future; - try { - future = pubClient.acquireTokenSilently(params); - return Mono.fromFuture(() -> future).map(result -> - new AccessToken(result.accessToken(), - result.expiresOnDate().toInstant().atOffset(ZoneOffset.UTC))); - - } catch (MalformedURLException e) { - e.printStackTrace(); - return Mono.error(new RuntimeException("Token was not found")); - } + }).switchIfEmpty( + Mono.defer(() -> identityClient.authenticateWithSharedTokenCache(request, username))) + .map(msalToken -> { + cachedToken.set(msalToken); + return msalToken; }); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 9270fcb60d5a6..c126756353000 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -11,7 +11,6 @@ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { private String username; - /** * Sets the username for the account. * @@ -24,12 +23,26 @@ public SharedTokenCacheCredentialBuilder username(String username) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public SharedTokenCacheCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + /** * Creates a new {@link SharedTokenCacheCredentialBuilder} with the current configurations. * * @return a {@link SharedTokenCacheCredentialBuilder} with the current configurations. */ public SharedTokenCacheCredential build() { - return new SharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); + return new SharedTokenCacheCredential(username, clientId, tenantId, + identityClientOptions.enablePersistentCache(true)); } } 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 5e40ce3385678..39f63b14913e7 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.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(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/UsernamePasswordCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java index 6ba055165c0e6..d1204b6bc8f40 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java @@ -36,6 +36,31 @@ public UsernamePasswordCredentialBuilder password(String password) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public UsernamePasswordCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. This is disabled by default. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public UsernamePasswordCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); + return this; + } + /** * Creates a new {@link UsernamePasswordCredential} with the current configurations. * 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 4d1f741d1d775..d7f6ec0e24f7c 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 @@ -57,7 +57,7 @@ class VisualStudioCodeCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(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 036cba27e5cf3..a9f08fddf7822 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 @@ -29,10 +29,13 @@ import com.microsoft.aad.msal4j.ClientCredentialParameters; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.DeviceCodeFlowParameters; +import com.microsoft.aad.msal4j.IAccount; 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; @@ -61,6 +64,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -89,7 +93,7 @@ public class IdentityClient { private final ClientLogger logger = new ClientLogger(IdentityClient.class); private final IdentityClientOptions options; - private final PublicClientApplication publicClientApplication; + private PublicClientApplication publicClientApplication; private final String tenantId; private final String clientId; private HttpPipelineAdapter httpPipelineAdapter; @@ -111,8 +115,14 @@ public class IdentityClient { this.tenantId = tenantId; this.clientId = clientId; this.options = options; - if (clientId == null) { - this.publicClientApplication = null; + } + + 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.")); } else { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId; PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); @@ -146,7 +156,21 @@ public class IdentityClient { if (options.getExecutorService() != null) { publicClientApplicationBuilder.executorService(options.getExecutorService()); } + 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.publicClientApplication = publicClientApplicationBuilder.build(); + return this.publicClientApplication; } } @@ -194,7 +218,7 @@ public Mono authenticateWithIntelliJ(TokenRequestContext request) { .builder(new HashSet<>(request.getScopes()), refreshToken) .build(); - return Mono.defer(() -> Mono.fromFuture(publicClientApplication.acquireToken(parameters)) + return Mono.defer(() -> Mono.fromFuture(getPublicClientApplication(false).acquireToken(parameters)) .map(ar -> new MsalToken(ar, options))); } else { @@ -228,7 +252,7 @@ public Mono authenticateWithAzureCli(TokenRequestContext request) { } command.append(scopes); - + AccessToken token = null; BufferedReader reader = null; try { @@ -310,7 +334,6 @@ public Mono authenticateWithAzureCli(TokenRequestContext request) { return Mono.just(token); } - /** * Asynchronously acquire a token from Active Directory with a client secret. * @@ -439,34 +462,47 @@ public Mono authenticateWithPemCertificate(String pemCertificatePat */ public Mono authenticateWithUsernamePassword(TokenRequestContext request, String username, String password) { - return Mono.fromFuture(publicClientApplication.acquireToken( + return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) - .map(ar -> new MsalToken(ar, options)); + .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with username and password", + null, t)).map(ar -> new MsalToken(ar, options)); } /** * Asynchronously acquire a token from the currently logged in client. * * @param request the details of the token request + * @param account the account used to login to acquire the last token * @return a Publisher that emits an AccessToken */ - public Mono authenticateWithUserRefreshToken(TokenRequestContext request, MsalToken msalToken) { - SilentParameters parameters; - if (msalToken.getAccount() != null) { - parameters = SilentParameters.builder(new HashSet<>(request.getScopes()), msalToken.getAccount()).build(); - } else { - parameters = SilentParameters.builder(new HashSet<>(request.getScopes())).build(); - - } - return Mono.defer(() -> { + public Mono authenticateWithMsalAccount(TokenRequestContext request, IAccount account) { + return Mono.defer(() -> Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())); + if (account != null) { + parametersBuilder = parametersBuilder.account(account); + } try { - return Mono.fromFuture(publicClientApplication.acquireTokenSilently(parameters)) - .map(ar -> new MsalToken(ar, options)); + return getPublicClientApplication(false) + .acquireTokenSilently(parametersBuilder.build()); } catch (MalformedURLException e) { - return Mono.error(e); + throw logger.logExceptionAsError(Exceptions.propagate(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); + } + try { + return getPublicClientApplication(false).acquireTokenSilently(forceParametersBuilder.build()); + } catch (MalformedURLException e) { + throw logger.logExceptionAsError(Exceptions.propagate(e)); + } + }).map(result -> new MsalToken(result, options)))); } /** @@ -485,8 +521,9 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder(new HashSet<>(request.getScopes()), dc -> deviceCodeConsumer.accept(new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); - return publicClientApplication.acquireToken(parameters); - }).map(ar -> new MsalToken(ar, options)); + return getPublicClientApplication(false).acquireToken(parameters); + }).onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with device code", null, t)) + .map(ar -> new MsalToken(ar, options)); } /** @@ -505,7 +542,7 @@ public Mono authenticateWithVsCodeCredential(TokenRequestContext requ .builder(new HashSet<>(request.getScopes()), credential) .build(); - return Mono.defer(() -> Mono.fromFuture(publicClientApplication.acquireToken(parameters)) + return Mono.defer(() -> Mono.fromFuture(getPublicClientApplication(false).acquireToken(parameters)) .map(ar -> new MsalToken(ar, options))); } @@ -519,11 +556,12 @@ public Mono authenticateWithVsCodeCredential(TokenRequestContext requ */ public Mono authenticateWithAuthorizationCode(TokenRequestContext request, String authorizationCode, URI redirectUrl) { - return Mono.fromFuture(() -> publicClientApplication.acquireToken( + return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( AuthorizationCodeParameters.builder(authorizationCode, redirectUrl) .scopes(new HashSet<>(request.getScopes())) .build())) - .map(ar -> new MsalToken(ar, options)); + .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with authorization code", + null, t)).map(ar -> new MsalToken(ar, options)); } /** @@ -570,6 +608,55 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re }); } + /** + * Gets token from shared token cache + * */ + public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { + // find if the Public Client app with the requested username exists + return Mono.fromFuture(() -> getPublicClientApplication(true).getAccounts()) + .onErrorMap(t -> new CredentialUnavailableException( + "Cannot get accounts from token cache. Error: " + t.getMessage(), t)) + .flatMap(set -> { + IAccount requestedAccount; + Map accounts = new HashMap<>(); // home account id -> account + + if (set.isEmpty()) { + return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " + + "authentication unavailable. No accounts were found in the cache.")); + } + + for (IAccount cached : set) { + if (username == null || username.equals(cached.username())) { + if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one + accounts.put(cached.homeAccountId(), cached); + } + } + } + + if (accounts.isEmpty()) { + // no more accounts after filtering, username must be set + return Mono.error(new RuntimeException(String.format("SharedTokenCacheCredential " + + "authentication unavailable. No account matching the specified username: %s was " + + "found in the cache.", username))); + } else if (accounts.size() > 1) { + if (username == null) { + return Mono.error(new RuntimeException("SharedTokenCacheCredential authentication " + + "unavailable. Multiple accounts were found in the cache. Use username and " + + "tenant id to disambiguate.")); + } else { + return Mono.error(new RuntimeException(String.format("SharedTokenCacheCredential " + + "authentication unavailable. Multiple accounts matching the specified username: " + + "%s were found in the cache.", username))); + } + } else { + requestedAccount = accounts.values().iterator().next(); + } + + + return authenticateWithMsalAccount(request, requestedAccount); + }); + } + /** * Asynchronously acquire a token from the App Service Managed Service Identity endpoint. * 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 8f52d7dc4bb3c..26e161fc3721d 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 @@ -8,7 +8,11 @@ import com.azure.core.http.ProxyOptions; import com.azure.core.util.Configuration; import com.azure.identity.KnownAuthorityHosts; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; +import com.sun.jna.Platform; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -20,6 +24,17 @@ */ 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 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_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_KEYRING_ATTR_NAME = "MsalClientID"; + private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; private String authorityHost; private int maxRetry; @@ -29,6 +44,16 @@ 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; /** @@ -39,6 +64,16 @@ 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; } /** @@ -160,7 +195,7 @@ public IdentityClientOptions setExecutorService(ExecutorService executorService) public ExecutorService getExecutorService() { return executorService; } - + /** * @return how long before the actual token expiry to refresh the token. */ @@ -196,6 +231,28 @@ 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(); + } + + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return The updated identity client options. + */ + public IdentityClientOptions allowUnencryptedCache(boolean allowUnencryptedCache) { + this.allowUnencryptedCache = allowUnencryptedCache; + return this; + } + /** * Specifies the database to extract IntelliJ cached credentials from. * @param keePassDatabasePath the database to extract intellij credentials from. @@ -207,6 +264,26 @@ public IdentityClientOptions setIntelliJKeePassDatabasePath(String keePassDataba } /** + * Gets if the shared token cache is disabled. + * @return if the shared token cache is disabled. + */ + public boolean isSharedTokenCacheEnabled() { + return this.sharedTokenCacheEnabled; + } + + /** + * Sets whether to enable using the shared token cache. This is disabled by default. + * + * @param enabled whether to enable using the shared token cache. + * + * @return The updated identity client options. + */ + public IdentityClientOptions enablePersistentCache(boolean enabled) { + this.sharedTokenCacheEnabled = enabled; + return this; + } + + /* * Get the KeePass database path. * @return the KeePass database path to extract inellij credentials from. */ diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java deleted file mode 100644 index 9d29e2666e0e1..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.nio.file.Files; -import java.util.Random; - -/** - * Cache lock for the persistent shared MSAL token cache - * - * Needed to maintain the integrity of the cache if multiple processes are trying to access it at the same time. - * */ -public class CacheLock { - - private int lockfileRetryWait = 100; - private int lockfileRetryCount = 60000 / lockfileRetryWait; - - private File lockFile; - - private FileOutputStream fos; - private FileChannel channel; - private FileLock lock = null; - - private File debugFile; - private String debugFilename = java.nio.file.Paths.get( - System.getProperty("user.dir"), "target", "debug").toString(); - private final boolean debugFlag; - - /** - * Default constructor to be used to initialize CacheLock - * - * @param lockfileName path of the lock file to be used - * */ - public CacheLock(String lockfileName) { - lockFile = new File(lockfileName); - debugFlag = false; - } - - /** - * Constructor to be used for debugging purposes - * Enables printing the actions for each process while using the cache lock - * - * @param lockfileName path of the lock file to be used - * @param id name of the current process so - * */ - public CacheLock(String lockfileName, String id) { - lockFile = new File(lockfileName); - debugFile = new File(debugFilename + id + ".txt"); - debugFlag = true; - } - - /** - * Tries to obtain the lock by creating a file lock on the provided lockFile - * If it cannot be obtained right away, it retries lockfileRetryCount = 60000 / lockfileRetryWait times - * - * @throws CacheLockNotObtainedException if the lock cannot be obtained after all these tries. - * */ - public void lock() throws CacheLockNotObtainedException { - try { - for (int tryCount = 0; tryCount < lockfileRetryCount; tryCount++) { - - if (debugFlag) { - try { - fos = new FileOutputStream(debugFile, true); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - - if (!lockFile.exists()) { // file doesn't already exist so now you have to make a new one - if (lockFile.createNewFile()) { - lockFile.deleteOnExit(); - - try { - channel = new RandomAccessFile(lockFile, "rw").getChannel(); - lock = channel.lock(); - - printToFileIfDebug("Locked!\n"); - return; //success - - } catch (OverlappingFileLockException e) { - printToFileIfDebug("overlap error\n"); - } catch (Exception e) { - printToFileIfDebug("something else went wrong.. general exception\n"); - } - - } else { - printToFileIfDebug("lockfile already exists\n"); - } - } else { - printToFileIfDebug("create new file failed"); - } - - printToFileIfDebug("retry\n"); - - try { - Random rand = new Random(System.currentTimeMillis()); - int offset = rand.nextInt(10); - // slight offset in case multiple threads/processes have the same wait time - Thread.sleep(lockfileRetryWait + offset); - } catch (InterruptedException ex) { - printToFileIfDebug("thread sleep issue"); - } - } - - } catch (IOException e) { - printToFileIfDebug("general exception, not sure what happened here...no retries\n"); - } - - throw new CacheLockNotObtainedException("Maximum retries used; could not obtain CacheLock"); - } - - /** - * Tries to unlock the file lock - * - * @return true if the file was unlocked, false otherwise - * */ - public boolean unlock() { - try { - lock.release(); - channel.close(); - Files.delete(java.nio.file.Paths.get(lockFile.getPath())); - - printToFileIfDebug("unlocked\n"); - - return true; - } catch (IOException e) { - printToFileIfDebug("not unlocked... IOException: " + e.getMessage()); - return false; - } - } - - /** - * If debugFlag is true, then this will print logs to the file, otherwise it will do nothing - */ - private void printToFileIfDebug(String message) { - if (debugFlag && fos != null) { - try { - fos.write(message.getBytes("UTF-8")); - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java deleted file mode 100644 index de36ebf7be389..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -/** - * Exception for when the {@link CacheLock} cannot be obtained when trying cacheLock.lock() - * */ -public class CacheLockNotObtainedException extends RuntimeException { - - /** - * Initializes CacheLockNotObtainedException - * - * @param message Error message - * */ - public CacheLockNotObtainedException(String message) { - super(message); - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java deleted file mode 100644 index 74d5377d19c79..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.core.util.logging.ClientLogger; -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.azure.identity.implementation.msalextensions.cachepersister.PlatformNotSupportedException; -import com.microsoft.aad.msal4j.ITokenCacheAccessAspect; -import com.microsoft.aad.msal4j.ITokenCacheAccessContext; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * Access Aspect for accessing the token cache - * Allows for notifications for the cache before/after access so the lock can be used - * */ -public class PersistentTokenCacheAccessAspect implements ITokenCacheAccessAspect { - - private CachePersister cachePersister; - private ClientLogger logger; - - /** - * Default constructor, creates a CachePersister object - * - * @throws IOException from errors in creating the CachePersister - * @throws PlatformNotSupportedException from errors in creating the CachePersister - * */ - public PersistentTokenCacheAccessAspect() throws RuntimeException, PlatformNotSupportedException { - logger = new ClientLogger(PersistentTokenCacheAccessAspect.class); - - cachePersister = new CachePersister.Builder().build(); - } - - /** - * Constructor with a custom CachePersister object - * - * @param customCachePersister - * */ - public PersistentTokenCacheAccessAspect(CachePersister customCachePersister) { - cachePersister = customCachePersister; - } - - /** - * Loads token cache to memory using CachePersister - deserialize data in file to Token Cache class - * - * @param iTokenCacheAccessContext - * */ - public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - - byte[] bytes = cachePersister.readCache(); - String data; - try { - data = new String(bytes, "UTF-8"); - } catch (UnsupportedEncodingException e) { - data = ""; - } - - iTokenCacheAccessContext.tokenCache().deserialize(data); - } - - /** - * Reads memory and writes to token cache file using CachePersister - * - * @param iTokenCacheAccessContext - * */ - public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - - if (iTokenCacheAccessContext.hasCacheChanged()) { - String newData = iTokenCacheAccessContext.tokenCache().serialize(); - try { - cachePersister.writeCache(newData.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - // don't update cache - logger.error("was not able to write to cache"); - } - } - } - - /** - * Wrapper method to delete cache - * */ - public void deleteCache() { - cachePersister.deleteCache(); - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java deleted file mode 100644 index 42d4ff0e5cb45..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister; - -import com.azure.identity.implementation.msalextensions.cachepersister.windows.WindowsDPAPICacheProtector; -import com.sun.jna.Platform; - -import java.io.IOException; - -/** - * Wrapper class for CacheProtector - * Determines the type of CacheProtector to use (if possible) and instantiates it - * Also contains wrapper methods for read, write, and delete cache - * */ -public final class CachePersister { - - private CacheProtectorBase cacheProtector; - - /** - * Default constructor - * */ - private CachePersister(CacheProtectorBase cacheProtector) { - this.cacheProtector = cacheProtector; - } - - /** - * Wrapper method for reading cache - * - * @return byte[] of cache contents - * */ - public byte[] readCache() { - return cacheProtector.readCache(); - } - - /** - * Wrapper method for writing to the cache - * - * @param data Cache contents - * */ - public void writeCache(byte[] data) { - cacheProtector.writeCache(data); - } - - public boolean deleteCache() { - return cacheProtector.deleteCache(); - } - - /** - * Builder for CachePersister class - * Creates appropriate file paths and account and service names, and calls createCacheProtector - * */ - public static class Builder { - - private String cacheLocation; - private String lockfileLocation; - - /** - * Default builder based on platform for cache file, and default service and account names - */ - public Builder() { - - // determine platform and create cache file location - if (Platform.isWindows()) { - cacheLocation = java.nio.file.Paths.get(System.getProperty("user.home"), - "AppData", "Local", ".IdentityService", "msal.cache").toString(); - } else { - cacheLocation = java.nio.file.Paths.get(System.getProperty("user.home"), - "msal.cache").toString(); - } - lockfileLocation = cacheLocation + ".lockfile"; -// -// serviceName = "Microsoft.Developer.IdentityService"; -// accountName = "MSALCache"; - } - - /** - * @return Builder with updated cacheLocation and lockfileLocation - * */ - public Builder cacheLocation(String cacheLocation) { - this.cacheLocation = cacheLocation; - this.lockfileLocation = cacheLocation + ".lockfile"; - return this; - } - - /** - * @return Builder with updated lockfileLocation - * */ - public Builder lockfileLocation(String lockfileLocation) { - this.lockfileLocation = lockfileLocation; - return this; - } - - /** - * Builds CachePersister with all the information passed into the Builder - * - * @return newly instantiated CachePersister - * */ - public CachePersister build() throws RuntimeException { - if (Platform.isWindows()) { - try { - return new CachePersister(new WindowsDPAPICacheProtector(cacheLocation, lockfileLocation)); - } catch (IOException e) { - throw new RuntimeException("IO Exception in creating the WindowsDPAPICacheProtector"); - } - } else { - throw new PlatformNotSupportedException("Platform is not supported"); - } - } - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java deleted file mode 100644 index 453043940e0fb..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister; - -import com.azure.core.util.logging.ClientLogger; -import com.azure.identity.implementation.msalextensions.CacheLock; -import com.azure.identity.implementation.msalextensions.CacheLockNotObtainedException; - -import java.io.IOException; - -/** - * Abstract class for Cache Protectors - * Provides methods to read and write cache while using a CacheLock - * */ -public abstract class CacheProtectorBase { - - private String lockfileLocation; - private CacheLock lock; - - private ClientLogger logger; - - /** - * Constructor - * initializes cacheLock - * */ - public CacheProtectorBase(String lockfileLocation) { - logger = new ClientLogger(CacheProtectorBase.class); - - this.lockfileLocation = lockfileLocation; - lock = new CacheLock(this.lockfileLocation); - } - - /** - * Obtains lock and uses unprotect() to read and decrypt contents of the cache - * - * @return byte[] contents of cache - * */ - public byte[] readCache() { - byte[] contents = null; - - try { - lock.lock(); - } catch (CacheLockNotObtainedException ex) { - logger.error("readCache() - Issue in locking"); - return contents; - } - - try { - contents = unprotect(); - } catch (IOException ex) { - logger.error("readCache() - Issue in reading"); - } - - lock.unlock(); - return contents; - } - - /** - * Obtains lock and uses protect() to read and encrypt contents of the cache - * - * @param data data to write to cache - * */ - public void writeCache(byte[] data) { - - try { - lock.lock(); - } catch (CacheLockNotObtainedException e) { - logger.error("writeCache() - Issue in locking"); - return; - } - - try { - protect(data); - } catch (IOException e) { - logger.error("writeCache() - Issue in writing"); - } - - lock.unlock(); - } - - /** - * Decrypts data from cache - * - * @return byte[] of cache contents - * - * Overwritten by subclasses; each OS handles differently - * */ - protected byte[] unprotect() throws IOException { - byte[] empty = {}; - return empty; - } - - /** - * Encrypts data and writes to cache - * - * @param data - * - * Overwritten by subclasses; each OS handles differently - * */ - protected void protect(byte[] data) throws IOException { - } - - /** - * Obtains lock and deletes cache using deleteCacheHelper() - * - * @return true if cache is deleted, false otherwise - * */ - public boolean deleteCache() { - try { - lock.lock(); - } catch (CacheLockNotObtainedException e) { - logger.error("deleteCache() - issue in locking"); - return false; - } - - deleteCacheHelper(); - lock.unlock(); - - return true; - } - - /** - * Overwritten by subclasses; each OS handles differently - * */ - protected void deleteCacheHelper() { - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java deleted file mode 100644 index 61bab965a1d5b..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister; - -/** - * Exception for when the current OS is not supported by {@link CachePersister} - * so the OS specific DPAPI cannot be used to encrypt the token cache. - * */ -public class PlatformNotSupportedException extends RuntimeException { - - /** - * Initializes PlatformNotSupportedException - * - * @param message Error message - * */ - public PlatformNotSupportedException(String message) { - super(message); - } - -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java deleted file mode 100644 index 97f3091ad8f39..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister.windows; - -import com.azure.identity.implementation.msalextensions.cachepersister.CacheProtectorBase; -import com.sun.jna.platform.win32.Crypt32Util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * Cache Protector for Windows which uses Windows DPAPI to encrypt the cache - * */ -public class WindowsDPAPICacheProtector extends CacheProtectorBase { - - private final String cacheFilename; - private File cacheFile; - - /** - * Constructor to initialize WindowsDPAPICacheProtector - * Calls super constructor to initialize lock - * - * @param cacheLocation - * @param lockfileLocation - * - * @throws IOException if cacheFile File isn't created - * */ - public WindowsDPAPICacheProtector(String cacheLocation, String lockfileLocation) throws IOException { - super(lockfileLocation); - cacheFilename = cacheLocation; - cacheFile = new File(cacheFilename); - - makeSureFileExists(); - } - - /** - * Uses DPAPI to read and decrypt cache contents - * - * @return byte[] cache contents - * */ - protected byte[] unprotect() throws IOException { - makeSureFileExists(); - - byte[] encryptedBytes = new byte[(int) cacheFile.length()]; - - try (FileInputStream stream = new FileInputStream(cacheFile)) { - int read = 0; - while (read != encryptedBytes.length) { - read += stream.read(encryptedBytes); - } - } - - byte[] decryptedBytes = Crypt32Util.cryptUnprotectData(encryptedBytes); - return decryptedBytes; - } - - /** - * Uses DPAPI to write and protect cache contents - * - * @param data contents to write to cache - * */ - protected void protect(byte[] data) throws IOException { - makeSureFileExists(); - - byte[] encryptedBytes = Crypt32Util.cryptProtectData(data); - - try (FileOutputStream stream = new FileOutputStream(cacheFile)) { - stream.write(encryptedBytes); - } - } - - /** - * Make sure file exists - and write " " if it was just created - * Just a backup in case the cache was deleted - * */ - private void makeSureFileExists() throws IOException { - if (!cacheFile.exists()) { - cacheFile.createNewFile(); - protect(" ".getBytes("UTF-8")); - } - } - - /** - * Deletes the cache file if it exists - * */ - public void deleteCacheHelper() { - if (cacheFile.exists()) { - cacheFile.delete(); - } - } - -} 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 0b9305627739b..b15a28f51478e 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.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(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/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java index 50405f6990b03..a01add160fb3e 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 @@ -25,7 +25,8 @@ @RunWith(PowerMockRunner.class) @PrepareForTest(fullyQualifiedNames = "com.azure.identity.*") -@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"}) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.net.ssl.*", + "io.netty.handler.ssl.*", "io.netty.buffer.*", "io.netty.channel.*"}) public class DefaultAzureCredentialTest { private final String tenantId = "contoso.com"; @@ -95,7 +96,7 @@ public void testUseManagedIdentityCredential() throws Exception { && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) .verifyComplete(); } - + @Test public void testUseAzureCliCredential() throws Exception { // setup @@ -113,6 +114,7 @@ public void testUseAzureCliCredential() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithAzureCli(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(Mono.empty()); + when(identityClient.authenticateWithSharedTokenCache(request, null)).thenReturn(Mono.empty()); when(identityClient.authenticateWithIntelliJ(request)).thenReturn(Mono.empty()); when(identityClient.authenticateWithVsCodeCredential(any(), any())).thenReturn(Mono.empty()); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); 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 126e3f5f1e667..8660390ff28db 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.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(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 5f89055c0861a..1b5e1e2a94a8d 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.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(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 b78ef63b8412b..bd75c2307e036 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.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(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.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(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.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(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 aa7fd71b7aef2..72adcc2c2f8d6 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 @@ -51,7 +51,7 @@ public void deviceCodeCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -66,7 +66,7 @@ public void browserCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -81,7 +81,7 @@ public void usernamePasswordCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -96,7 +96,7 @@ public void authCodeCanGetToken() throws Exception { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(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 1e942dbd516f7..72fe05076ecc4 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 @@ -169,7 +169,8 @@ public void testValidDeviceCodeFlow() throws Exception { mockForDeviceCodeFlow(request, accessToken, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions(); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); AccessToken token = client.authenticateWithDeviceCode(request, deviceCodeChallenge -> { /* do nothing */ }).block(); Assert.assertEquals(accessToken, token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); @@ -237,7 +238,8 @@ public void testAuthorizationCodeFlow() throws Exception { mockForAuthorizationCodeFlow(token1, request, expiresAt); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions(); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithAuthorizationCode(request, authCode1, redirectUri)) .expectNextMatches(accessToken -> token1.equals(accessToken.getToken()) && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) @@ -256,8 +258,9 @@ public void testUserRefreshTokenflow() throws Exception { mockForUserRefreshTokenFlow(token2, request2, expiresAt); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); - StepVerifier.create(client.authenticateWithUserRefreshToken(request2, TestUtils.getMockMsalToken(token1, expiresAt).block())) + 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())) .expectNextMatches(accessToken -> token2.equals(accessToken.getToken()) && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) .verifyComplete(); @@ -276,7 +279,8 @@ public void testUsernamePasswordCodeFlow() throws Exception { mockForUsernamePasswordCodeFlow(token, request, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions(); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUsernamePassword(request, username, password)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) && expiresOn.getSecond() == accessToken.getExpiresAt().getSecond()) @@ -297,7 +301,8 @@ public void testBrowserAuthenicationCodeFlow() throws Exception { mocForBrowserAuthenticationCodeFlow(); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions(); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithBrowserInteraction(request, 4567)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) && expiresOn.getSecond() == accessToken.getExpiresAt().getSecond()) diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java deleted file mode 100644 index f28d76a8aef3a..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.sun.jna.Platform; -import org.junit.*; - -import java.io.*; -import java.util.Stack; -import java.util.stream.Collectors; - -public class CacheLockTest { - - private static String folder; - private static String testerFilename; - private static String lockfile; - - @BeforeClass - public static void setup() { - // get proper file paths - String currDir = System.getProperty("user.dir"); - String home = System.getProperty("user.home"); - - java.nio.file.Path classes = java.nio.file.Paths.get(currDir, "target", "classes"); - java.nio.file.Path tests = java.nio.file.Paths.get(currDir, "target", "test-classes"); - - testerFilename = java.nio.file.Paths.get(home, "tester.txt").toString(); - lockfile = java.nio.file.Paths.get(home, "testlock.lockfile").toString(); - - String delimiter = ":"; - if (Platform.isWindows()) { - delimiter = ";"; - } - folder = classes.toString() + delimiter + tests; - } - - @Test - public void tenThreadsWritingToFile() throws IOException { - - // make sure tester.json file doesn't already exist - File tester = new File(testerFilename); - tester.delete(); - - // delete the lock file just in case before starting - File lock = new File(lockfile); - lock.delete(); - - FileWriter a = new FileWriter("a", lockfile, testerFilename); - FileWriter b = new FileWriter("b", lockfile, testerFilename); - FileWriter c = new FileWriter("c", lockfile, testerFilename); - FileWriter d = new FileWriter("d", lockfile, testerFilename); - FileWriter e = new FileWriter("e", lockfile, testerFilename); - FileWriter f = new FileWriter("f", lockfile, testerFilename); - FileWriter g = new FileWriter("g", lockfile, testerFilename); - FileWriter h = new FileWriter("h", lockfile, testerFilename); - FileWriter i = new FileWriter("i", lockfile, testerFilename); - FileWriter j = new FileWriter("j", lockfile, testerFilename); - - try { - a.t.join(); - b.t.join(); - c.t.join(); - d.t.join(); - e.t.join(); - f.t.join(); - g.t.join(); - h.t.join(); - i.t.join(); - j.t.join(); - } catch (Exception ex) { - System.out.printf("Error with threads"); - } - - Stack stack = new Stack<>(); - int popped = 0; - - File file = new File(testerFilename); - - if (file.exists()) { - FileReader reader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(reader); - StringBuffer stringBuffer = new StringBuffer(); - String line; - while ((line = bufferedReader.readLine()) != null) { - String[] tokens = line.split(" "); - if (tokens[0].equals("<")) { // enter - stack.push(tokens[1]); - } else if (tokens[0].equals(">")) { // exit - if (stack.peek().equals(tokens[1])) { - stack.pop(); - popped++; - } else { - System.out.println("messed up: " + tokens[1]); - } - } - } - reader.close(); - - if (!stack.empty()) { - Assert.fail(); - } - } else { - Assert.fail("File does not exist"); - } - - Assert.assertEquals("10 processes didn't write", popped, 10); - - } - - @Ignore("Run local only - CI does not support classpath well") - public void tenProcessesWritingToFile() throws IOException, InterruptedException { - // make sure tester.json file doesn't already exist - File tester = new File(testerFilename); - tester.delete(); - - // delete the lock file just in case before starting - File lock = new File(lockfile); - lock.delete(); - - String mainClass = com.azure.identity.implementation.msalextensions.FileWriter.class.getName(); - Process process1 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(1), lockfile, testerFilename}).start(); - Process process2 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(2), lockfile, testerFilename}).start(); - Process process3 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(3), lockfile, testerFilename}).start(); - Process process4 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(4), lockfile, testerFilename}).start(); - Process process5 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(5), lockfile, testerFilename}).start(); - Process process6 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(6), lockfile, testerFilename}).start(); - Process process7 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(7), lockfile, testerFilename}).start(); - Process process8 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(8), lockfile, testerFilename}).start(); - Process process9 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(9), lockfile, testerFilename}).start(); - Process process10 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(10), lockfile, testerFilename}).start(); - - waitForProcess(process1); - waitForProcess(process2); - waitForProcess(process3); - waitForProcess(process4); - waitForProcess(process5); - waitForProcess(process6); - waitForProcess(process7); - waitForProcess(process8); - waitForProcess(process9); - waitForProcess(process10); - - Stack stack = new Stack<>(); - int popped = 0; - - File file = new File(testerFilename); - if (file.exists()) { - FileReader reader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(reader); - StringBuffer stringBuffer = new StringBuffer(); - String line; - while ((line = bufferedReader.readLine()) != null) { - String[] tokens = line.split(" "); - if (tokens[0].equals("<")) { // enter - stack.push(tokens[1]); - } else if (tokens[0].equals(">")) { // exit - if (stack.peek().equals(tokens[1])) { - stack.pop(); - popped++; - } else { - System.out.println("messed up: " + tokens[1]); - } - } - } - reader.close(); - - if (!stack.empty()) { - Assert.fail(); - } - } else { - Assert.fail("File does not exist"); - } - - Assert.assertEquals("10 processes didn't write", popped, 10); - } - - private void waitForProcess(Process process) throws InterruptedException { - if (process.waitFor() != 0) { - throw new RuntimeException(new BufferedReader(new InputStreamReader(process.getErrorStream())) - .lines().collect(Collectors.joining("\n"))); - } - } - - /* - * Class to be used for testing threads - * */ - class FileWriter implements Runnable { - - String threadName; - File file; - String lockfile; - Thread t; - - FileWriter(String threadName, String lockfile, String filename) { - this.threadName = threadName; - this.lockfile = lockfile; - this.file = new File(filename); - - t = new Thread(this, threadName); - t.start(); - } - - public void run() { - CacheLock lock = new CacheLock(lockfile); - try { - lock.lock(); - try { - if (!file.exists()) { - file.createNewFile(); - } - FileOutputStream os = new FileOutputStream(file, true); - - os.write(("< " + threadName + "\n").getBytes()); - Thread.sleep(1000); - os.write(("> " + threadName + "\n").getBytes()); - - os.close(); - } catch (Exception ex) { - - } - } catch (Exception ex) { - System.out.println("Couldn't obtain lock"); - } finally { - try { - lock.unlock(); - } catch (Exception ex) { - System.out.println("aljsdladsk"); - } - } - - - } - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java deleted file mode 100644 index 5ff72cc819083..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.microsoft.aad.msal4j.ClientCredentialFactory; -import com.microsoft.aad.msal4j.ClientCredentialParameters; -import com.microsoft.aad.msal4j.ConfidentialClientApplication; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.CompletableFuture; - -/* - * Before running these tests, log into Azure with the new Visual Studio 16.3.0 Preview 1 - * This should create a msal.cache file in the the user directory, and these tests should be able to read and - * write from the same file. - * Note that deleting this cache file will cause the user to have to re-log in with Visual Studio as this will delete - * the tokens in the cache - * - * NOTE: These tests are written assuming that nothing else has written to the MSAL cache besides visual studio - * */ -public class CrossProgramVSTest { - - CachePersister cachePersister; - PersistentTokenCacheAccessAspect accessAspect; - - private ConfidentialClientApplication confApp; - private ClientCredentialParameters confParameters; - - private int count = 0; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue("Skipping these tests until we mock or record it", false); - //using the default cachepersister and accessAspect objects - cachePersister = new CachePersister.Builder().build(); - accessAspect = new PersistentTokenCacheAccessAspect(); - - confApp = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confParameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE)) - .build(); - } - - @Test - public void readCacheAfterVSAzureLogin() { - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.has("AccessToken")); - Assert.assertTrue(jsonObj.has("RefreshToken")); - Assert.assertTrue(jsonObj.has("IdToken")); - Assert.assertTrue(jsonObj.has("Account")); - Assert.assertTrue(jsonObj.has("AppMetadata")); - - System.out.println(currJson); - - count = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - } - - @Test - public void writeToSameCacheFileAfterVSAzureLogin() { - String currJson = new String(cachePersister.readCache()); - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - int set = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - CompletableFuture result = confApp.acquireToken(confParameters); - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - currJson = new String(cachePersister.readCache()); - jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - int newSet = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - Assert.assertEquals(newSet, set + 1); - count++; - - System.out.println(currJson); - } - - @Test - public void countCache() { - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - int newSet = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - System.out.println(newSet); - } - - @Test - public void readCacheAfterPowershellAzureLogin() { - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - System.out.println(currJson); - - int newSet = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - Assert.assertEquals(newSet, 6); - count++; - } - -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java deleted file mode 100644 index 17dd249c7493f..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import java.io.File; -import java.io.FileOutputStream; - -public class FileWriter { - - public static void main(String[] args) throws Exception { - File file; - String lockfile; - - if (args.length == 3) { - lockfile = args[1]; - file = new File(args[2]); - } else { - System.out.println("wrong number of args lol????"); - return; - } - CacheLock lock = new CacheLock(lockfile); - - int retries = 3; - boolean succeeded = false; - while (retries-- > 0 && !succeeded) { - try { - lock.lock(); - - if (!file.exists()) { - file.createNewFile(); - } - FileOutputStream os = new FileOutputStream(file, true); - - os.write(("< " + args[0] + "\n").getBytes()); - Thread.sleep(1000); - os.write(("> " + args[0] + "\n").getBytes()); - - os.close(); - succeeded = true; - } finally { - lock.unlock(); - } - } - - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java deleted file mode 100644 index 90193076612eb..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.sun.jna.Platform; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -public class MsalCacheStorageTest { - - private CachePersister cachePersister; - private String cacheLocation; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue(Platform.isWindows()); - cacheLocation = java.nio.file.Paths.get(System.getProperty("user.home"), "test.cache").toString(); - cachePersister = new CachePersister.Builder() - .cacheLocation(cacheLocation) - .lockfileLocation(cacheLocation + ".lockfile") - .build(); - } - - @Test - public void writesReadsCacheData() { - try { - File f = new File(cacheLocation); - - String testString = "hello world"; - - cachePersister.writeCache(testString.getBytes()); - String receivedString = new String(cachePersister.readCache()); - - Assert.assertEquals(receivedString, testString); - - cachePersister.deleteCache(); - } finally { - cachePersister.deleteCache(); - } - } - -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java deleted file mode 100644 index 667b295314ed9..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.microsoft.aad.msal4j.*; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -public class MultithreadedTokenCacheTest { - - private PersistentTokenCacheAccessAspect accessAspect; - private CachePersister cachePersister; - - private ConfidentialClientApplication confApp; - private ConfidentialClientApplication confApp2; - private PublicClientApplication pubApp; - private ClientCredentialParameters confParameters; - private DeviceCodeFlowParameters pubParameters; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue("Skipping these tests until we mock or record it", false); - // custom MsalCacheStorage for testing purposes so we don't overwrite the real one - cachePersister = new CachePersister.Builder() - .cacheLocation(java.nio.file.Paths.get(System.getProperty("user.home"), "test.cache").toString()) - .build(); - - accessAspect = new PersistentTokenCacheAccessAspect(cachePersister); - - confApp = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confApp2 = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID_2, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET_2)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confParameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE)) - .build(); - - - pubApp = PublicClientApplication.builder(TestConfiguration.PUBLIC_CLIENT_ID) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> System.out.println(deviceCode.message()); - - pubParameters = DeviceCodeFlowParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE), - deviceCodeConsumer) - .build(); - } - - @After - public void cleanup() { - if (accessAspect != null) { - accessAspect.deleteCache(); - } - } - - @Test - public void twoThreadsWritingTokens() { - - ConcurrentClient a = new ConcurrentClient("conf"); - ConcurrentClient b = new ConcurrentClient("pub"); - - try { - a.t.join(); - b.t.join(); - } catch (Exception e) { - System.out.printf("Error with threads"); - } - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 2); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void tenThreadsWritingSameConfTokens() { - - ConcurrentClient a = new ConcurrentClient("conf"); - ConcurrentClient b = new ConcurrentClient("conf"); - ConcurrentClient c = new ConcurrentClient("conf"); - ConcurrentClient d = new ConcurrentClient("conf"); - ConcurrentClient e = new ConcurrentClient("conf"); - ConcurrentClient f = new ConcurrentClient("conf"); - ConcurrentClient g = new ConcurrentClient("conf"); - ConcurrentClient h = new ConcurrentClient("conf"); - ConcurrentClient i = new ConcurrentClient("conf"); - ConcurrentClient j = new ConcurrentClient("conf"); - - try { - a.t.join(); - b.t.join(); - c.t.join(); - d.t.join(); - e.t.join(); - f.t.join(); - g.t.join(); - h.t.join(); - i.t.join(); - j.t.join(); - } catch (Exception ex) { - System.out.printf("Error with threads"); - } - - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - - System.out.println("keys: " + jsonObj.get("AccessToken").getAsJsonObject().keySet().size()); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 0); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 0); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 0); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 0); - - accessAspect.deleteCache(); - } - - class ConcurrentClient implements Runnable { - - String threadName; - Thread t; - - ConcurrentClient(String threadName) { - this.threadName = threadName; - t = new Thread(this, threadName); - t.start(); - } - - public void run() { - - if (threadName.equals("conf")) { - CompletableFuture result = confApp.acquireToken(confParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 1 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - } else if (threadName.equals("pub")) { - - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 2 - " + ex.getMessage()); - return "Unknown!"; - } - - return res; - - }).join(); - } - } - - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java deleted file mode 100644 index b9afcbaaed4f4..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.microsoft.aad.msal4j.*; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -public class PersistentTokenCacheAccessAspectTest { - - private PersistentTokenCacheAccessAspect accessAspect; - private CachePersister cachePersister; - - private ConfidentialClientApplication confApp; - private ConfidentialClientApplication confApp2; - private PublicClientApplication pubApp; - private ClientCredentialParameters confParameters; - private DeviceCodeFlowParameters pubParameters; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue("Skipping these tests until we mock or record it", false); - // custom MsalCacheStorage for testing purposes so we don't overwrite the real one - cachePersister = new CachePersister.Builder() - .cacheLocation(java.nio.file.Paths.get(System.getProperty("user.home"), "test.cache").toString()) - .build(); - - accessAspect = new PersistentTokenCacheAccessAspect(cachePersister); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { - System.out.println(deviceCode.message()); - }; - - confApp = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confApp2 = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID_2, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET_2)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - pubApp = PublicClientApplication.builder(TestConfiguration.PUBLIC_CLIENT_ID) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confParameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE)) - .build(); - - pubParameters = DeviceCodeFlowParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE), - deviceCodeConsumer) - .build(); - } - - @After - public void cleanup() { - if (accessAspect != null) { - accessAspect.deleteCache(); - } - } - - @Test - public void checkIfWritesToFileFirstTimeConfidentialClient() { - - CompletableFuture result = confApp.acquireToken(confParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.has("AccessToken")); - Assert.assertTrue(jsonObj.has("RefreshToken")); - Assert.assertTrue(jsonObj.has("IdToken")); - Assert.assertTrue(jsonObj.has("Account")); - Assert.assertTrue(jsonObj.has("AppMetadata")); - - int set = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - Assert.assertEquals(set, 1); - - accessAspect.deleteCache(); - } - - @Test - public void checkIfWritesToFileFirstTimePublicClient() { - - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.has("AccessToken")); - Assert.assertTrue(jsonObj.has("RefreshToken")); - Assert.assertTrue(jsonObj.has("IdToken")); - Assert.assertTrue(jsonObj.has("Account")); - Assert.assertTrue(jsonObj.has("AppMetadata")); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void addsAccountToListPubClient() { - - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - - return res; - - }).join(); - - Assert.assertEquals(pubApp.getAccounts().join().size(), 1); - - accessAspect.deleteCache(); - } - - @Test - public void writesTwoTokensToCache() { - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 1 - " + ex.getMessage()); - return "Unknown!"; - } - - return res; - - }).join(); - - CompletableFuture result2 = confApp.acquireToken(confParameters); - - result2.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 2 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 2); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void writesReadsMultipleTokensToCache() { - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 1 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - CompletableFuture result2 = confApp.acquireToken(confParameters); - - result2.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 2 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - CompletableFuture result3 = confApp2.acquireToken(confParameters); - - result3.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 3 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 3); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void syncsCacheWithExpiredTokens() { - CompletableFuture result3 = confApp2.acquireToken(confParameters); - - result3.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 3 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - accessAspect.deleteCache(); - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java deleted file mode 100644 index 8841df4f0557a..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -public class TestConfiguration { - - static final String TENANT_SPECIFIC_AUTHORITY = "https://login.microsoftonline.com/[insert here]/"; - - static final String PUBLIC_CLIENT_ID = ""; - - static final String GRAPH_DEFAULT_SCOPE = "https://graph.windows.net/.default"; - - static final String CONFIDENTIAL_CLIENT_ID = ""; - static final String CONFIDENTIAL_CLIENT_ID_2 = ""; - - static final String CONFIDENTIAL_CLIENT_SECRET = ""; - static final String CONFIDENTIAL_CLIENT_SECRET_2 = ""; -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java index a041b0977c43f..896d47c37bcd7 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java @@ -87,6 +87,17 @@ public static Mono getMockMsalToken(String accessToken, OffsetDateTim .map(ar -> new MsalToken(ar, new IdentityClientOptions())); } + /** + * Creates a mock {@link IAccount} instance. + * @param accessToken the access token to return + * @param expiresOn the expiration time + * @return a Mono publisher of the result + */ + public static Mono getMockMsalAccount(String accessToken, OffsetDateTime expiresOn) { + return Mono.fromFuture(getMockAuthenticationResult(accessToken, expiresOn)) + .map(IAuthenticationResult::account); + } + /** * Creates a mock {@link AccessToken} instance. * @param accessToken the access token to return diff --git a/sdk/identity/pom.xml b/sdk/identity/pom.xml index b9f57fa47d469..98648ba66804a 100644 --- a/sdk/identity/pom.xml +++ b/sdk/identity/pom.xml @@ -12,5 +12,6 @@ azure-identity + azure-identity-perf