From da3a5b7ddbc1de2f3581cfb4a63bb0b7c686940a Mon Sep 17 00:00:00 2001 From: g2vinay Date: Mon, 2 Aug 2021 03:26:28 -0700 Subject: [PATCH 1/8] update --- .../azure/identity/OnBehalfOfCredential.java | 47 +++++++++++++++++++ .../identity/OnBehalfOfCredentialBuilder.java | 29 ++++++++++++ .../azure/identity/UserAssertionScope.java | 28 +++++++++++ .../identity/UserAssertionScopeOptions.java | 4 ++ .../implementation/IdentityClient.java | 37 ++++++++------- 5 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java new file mode 100644 index 000000000000..7dde7e7b4076 --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java @@ -0,0 +1,47 @@ +package com.azure.identity; + +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; +import com.azure.identity.implementation.IdentityClient; +import com.azure.identity.implementation.IdentityClientBuilder; +import com.azure.identity.implementation.IdentityClientOptions; +import reactor.core.publisher.Mono; + +public class OnBehalfOfCredential implements TokenCredential { + private String clientId; + private String tenantId; + private String clientSecret; + private IdentityClientOptions options; + + public OnBehalfOfCredential(String clientId, String tenantId, String clientSecret, + IdentityClientOptions options) { + this.clientId = clientId; + this.tenantId = tenantId; + this.clientSecret = clientSecret; + this.options = options; + } + + @Override + public Mono getToken(TokenRequestContext request) { + return Mono.defer(() -> { + UserAssertionScope.currentScope.client = new IdentityClientBuilder() + .clientId(clientId) + .tenantId(tenantId) + .clientSecret(clientSecret) + .identityClientOptions(options) + .build(); + + +// _client ?? new MsalConfidentialClient(_pipeline, tenantId, _clientId, _clientSecret, UserAssertionScope.Current.CacheOptions, default); + + return UserAssertionScope.currentScope.client.authenticateWithOBO(request, UserAssertionScope.currentScope.UserAssertion); + +// UserAssertionScope.Current.Client +// .AcquireTokenOnBehalfOf(requestContext.Scopes, tenantId, UserAssertionScope.Current.UserAssertion, async, cancellationToken) +// .ConfigureAwait(false); +// +// return new AccessToken(result.AccessToken, result.ExpiresOn); + }); + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java new file mode 100644 index 000000000000..83b63d6b1692 --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java @@ -0,0 +1,29 @@ +package com.azure.identity; + +public class OnBehalfOfCredentialBuilder extends AadCredentialBuilderBase { + private String clientSecret; + + /** + * Sets the client secret for the authentication. + * @param clientSecret the secret value of the AAD application. + * @return An updated instance of this builder. + */ + public OnBehalfOfCredentialBuilder clientSecret(String clientSecret) { + this.clientSecret = clientSecret; + return this; + } + + /** + * Configures the persistent shared token cache options and enables the persistent token cache which is disabled + * by default. If configured, the credential will store tokens in a cache persisted to the machine, protected to + * the current user, which can be shared by other credentials and processes. + * + * @param tokenCachePersistenceOptions the token cache configuration options + * @return An updated instance of this builder with the token cache options configured. + */ + public OnBehalfOfCredentialBuilder tokenCachePersistenceOptions(TokenCachePersistenceOptions + tokenCachePersistenceOptions) { + this.identityClientOptions.setTokenCacheOptions(tokenCachePersistenceOptions); + return this; + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java new file mode 100644 index 000000000000..f2f2061793be --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java @@ -0,0 +1,28 @@ +package com.azure.identity; + +import com.azure.identity.implementation.IdentityClient; +import com.microsoft.aad.msal4j.UserAssertion; + +public class UserAssertionScope implements AutoCloseable { + + static volatile UserAssertionScope currentScope; + UserAssertion UserAssertion; + IdentityClient client; +// internal ITokenCacheOptions CacheOptions { get; } + + /** + * Initializes a new instance of {@link UserAssertionScope} using the supplied access token. + * @param accessToken The access token that will be used by {@link OnBehalfOfCredential} as the user assertion when requesting On-Behalf-Of tokens. + * @param options The {@link UserAssertionScopeOptions}to configure this instance. + */ + public UserAssertionScope(String accessToken, UserAssertionScopeOptions options) + { + UserAssertion = new UserAssertion(accessToken); + currentScope = this; +// CacheOptions = options ? new UserAssertionCacheOptions(options); + } + @Override + public void close() throws Exception { + + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java new file mode 100644 index 000000000000..1a3284006a95 --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java @@ -0,0 +1,4 @@ +package com.azure.identity; + +public class UserAssertionScopeOptions { +} 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 f9403eb364c8..be4c4a202f6c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -30,22 +30,7 @@ import com.azure.identity.implementation.util.IdentitySslUtil; import com.azure.identity.implementation.util.ScopeUtil; import com.fasterxml.jackson.databind.JsonNode; -import com.microsoft.aad.msal4j.AuthorizationCodeParameters; -import com.microsoft.aad.msal4j.ClaimsRequest; -import com.microsoft.aad.msal4j.ClientCredentialFactory; -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.IAuthenticationResult; -import com.microsoft.aad.msal4j.IClientCredential; -import com.microsoft.aad.msal4j.InteractiveRequestParameters; -import com.microsoft.aad.msal4j.MsalInteractionRequiredException; -import com.microsoft.aad.msal4j.Prompt; -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.msal4j.*; import com.sun.jna.Platform; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -525,6 +510,26 @@ public Mono authenticateWithAzurePowerShell(TokenRequestContext req })); } + + /** + * Asynchronously acquire a token from Active Directory with Azure Power Shell. + * + * @param request the details of the token request + * @return a Publisher that emits an AccessToken + */ + public Mono authenticateWithOBO(TokenRequestContext request, UserAssertion userAssertion) { + + return confidentialClientApplicationAccessor.getValue() + .flatMap(confidentialClient -> { + + return Mono.fromFuture(() -> confidentialClient.acquireToken(OnBehalfOfParameters + .builder(new HashSet<>(request.getScopes()), userAssertion) + .build())) + .map(MsalToken::new); + }); + } + + private Mono getAccessTokenFromPowerShell(TokenRequestContext request, PowershellManager powershellManager) { return powershellManager.initSession() From a12a83e486c932fca3876d7b8106f5e83b6f52f8 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Mon, 23 Aug 2021 06:22:36 -0700 Subject: [PATCH 2/8] update --- .../credential/AccessTokenExperimental.java | 42 +++++++++++++++++++ .../implementation/AccessTokenCacheImpl.java | 7 ++-- .../azure/identity/OnBehalfOfCredential.java | 42 +++++++++++-------- .../azure/identity/UserAssertionScope.java | 17 ++------ .../implementation/IdentityClient.java | 11 ++--- 5 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java new file mode 100644 index 000000000000..42792e65fb97 --- /dev/null +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java @@ -0,0 +1,42 @@ +package com.azure.core.experimental.credential; + +import com.azure.core.credential.AccessToken; + +import java.time.OffsetDateTime; + +public class AccessTokenExperimental extends AccessToken { + private OffsetDateTime refreshOn; + + /** + * Creates an access token instance. + * + * @param token the token string. + * @param expiresAt the expiration time. + */ + public AccessTokenExperimental(String token, OffsetDateTime expiresAt) { + super(token, expiresAt); + if (expiresAt.compareTo(OffsetDateTime.MIN.plusMinutes(5)) > 0) { + refreshOn = expiresAt.minusMinutes(5); + } else { + refreshOn = expiresAt; + } + } + + /** + * Creates an access token instance. + * + * @param token the token string. + * @param expiresAt the expiration time. + */ + public AccessTokenExperimental(String token, OffsetDateTime expiresAt, OffsetDateTime refreshOn) { + super(token, expiresAt); + this.refreshOn = refreshOn; + } + + /** + * @return the time when the token should be refreshed, in UTC. + */ + public OffsetDateTime getRefreshOn() { + return refreshOn; + } +} diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java index a988dbb94393..a6646ffd894b 100644 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; +import com.azure.core.experimental.credential.AccessTokenExperimental; import com.azure.core.util.logging.ClientLogger; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -27,7 +28,7 @@ public class AccessTokenCacheImpl { // The delay after a refresh to attempt another token refresh private static final Duration REFRESH_DELAY = Duration.ofSeconds(30); // the offset before token expiry to attempt proactive token refresh - private static final Duration REFRESH_OFFSET = Duration.ofMinutes(5); +// private static final Duration REFRESH_OFFSET = Duration.ofMinutes(5); private volatile AccessToken cache; private volatile OffsetDateTime nextTokenRefresh = OffsetDateTime.now(); private final AtomicReference> wip; @@ -45,8 +46,8 @@ public AccessTokenCacheImpl(TokenCredential tokenCredential) { Objects.requireNonNull(tokenCredential, "The token credential cannot be null"); this.wip = new AtomicReference<>(); this.tokenCredential = tokenCredential; - this.shouldRefresh = accessToken -> OffsetDateTime.now() - .isAfter(accessToken.getExpiresAt().minus(REFRESH_OFFSET)); + this.shouldRefresh = accessToken -> OffsetDateTime.now().isAfter(accessToken instanceof AccessTokenExperimental + ? ((AccessTokenExperimental) accessToken).getRefreshOn() : accessToken.getExpiresAt().minusMinutes(5)); } /** diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java index 7dde7e7b4076..2afcf642d4e5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java @@ -3,16 +3,24 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; +import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; +import com.azure.identity.implementation.util.LoggingUtil; import reactor.core.publisher.Mono; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + public class OnBehalfOfCredential implements TokenCredential { private String clientId; private String tenantId; private String clientSecret; private IdentityClientOptions options; + private volatile HashMap userAssertionClientMap; + private final ClientLogger logger = new ClientLogger(OnBehalfOfCredential.class); + public OnBehalfOfCredential(String clientId, String tenantId, String clientSecret, IdentityClientOptions options) { @@ -20,28 +28,26 @@ public OnBehalfOfCredential(String clientId, String tenantId, String clientSecre this.tenantId = tenantId; this.clientSecret = clientSecret; this.options = options; + this.userAssertionClientMap = new HashMap<>(); } @Override public Mono getToken(TokenRequestContext request) { - return Mono.defer(() -> { - UserAssertionScope.currentScope.client = new IdentityClientBuilder() - .clientId(clientId) - .tenantId(tenantId) - .clientSecret(clientSecret) - .identityClientOptions(options) - .build(); - - -// _client ?? new MsalConfidentialClient(_pipeline, tenantId, _clientId, _clientSecret, UserAssertionScope.Current.CacheOptions, default); - - return UserAssertionScope.currentScope.client.authenticateWithOBO(request, UserAssertionScope.currentScope.UserAssertion); - -// UserAssertionScope.Current.Client -// .AcquireTokenOnBehalfOf(requestContext.Scopes, tenantId, UserAssertionScope.Current.UserAssertion, async, cancellationToken) -// .ConfigureAwait(false); -// -// return new AccessToken(result.AccessToken, result.ExpiresOn); + return Mono.deferContextual(ctx -> { + UserAssertionScope scope = ctx.get(UserAssertionScope.USER_ASSERTION_SCOPE_KEY); + IdentityClient client = userAssertionClientMap.containsKey(scope) ? userAssertionClientMap.get(scope) + : userAssertionClientMap.put(scope, new IdentityClientBuilder() + .tenantId(tenantId) + .clientId(clientId) + .clientSecret(clientSecret) + .identityClientOptions(options) + .build()); + + return client.authenticateWithConfidentialClientCache(request) + .onErrorResume(t -> Mono.empty()) + .switchIfEmpty(Mono.defer(() -> client.authenticateWithOBO(request, scope.UserAssertion))) + .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) + .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); }); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java index f2f2061793be..5681c56f1eea 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java @@ -3,26 +3,15 @@ import com.azure.identity.implementation.IdentityClient; import com.microsoft.aad.msal4j.UserAssertion; -public class UserAssertionScope implements AutoCloseable { - - static volatile UserAssertionScope currentScope; +public class UserAssertionScope { + public static String USER_ASSERTION_SCOPE_KEY = "USER_ASSERTION_SCOPE_KEY"; UserAssertion UserAssertion; - IdentityClient client; -// internal ITokenCacheOptions CacheOptions { get; } /** * Initializes a new instance of {@link UserAssertionScope} using the supplied access token. * @param accessToken The access token that will be used by {@link OnBehalfOfCredential} as the user assertion when requesting On-Behalf-Of tokens. - * @param options The {@link UserAssertionScopeOptions}to configure this instance. */ - public UserAssertionScope(String accessToken, UserAssertionScopeOptions options) - { + public UserAssertionScope(String accessToken) { UserAssertion = new UserAssertion(accessToken); - currentScope = this; -// CacheOptions = options ? new UserAssertionCacheOptions(options); - } - @Override - public void close() throws Exception { - } } 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 be4c4a202f6c..e794e2235d54 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 @@ -520,13 +520,10 @@ public Mono authenticateWithAzurePowerShell(TokenRequestContext req public Mono authenticateWithOBO(TokenRequestContext request, UserAssertion userAssertion) { return confidentialClientApplicationAccessor.getValue() - .flatMap(confidentialClient -> { - - return Mono.fromFuture(() -> confidentialClient.acquireToken(OnBehalfOfParameters - .builder(new HashSet<>(request.getScopes()), userAssertion) - .build())) - .map(MsalToken::new); - }); + .flatMap(confidentialClient -> Mono.fromFuture(() -> confidentialClient.acquireToken(OnBehalfOfParameters + .builder(new HashSet<>(request.getScopes()), userAssertion) + .build())) + .map(MsalToken::new)); } From 9b8852eb66b7edb15869bc645ecc01dc137d7e0c Mon Sep 17 00:00:00 2001 From: g2vinay Date: Wed, 1 Sep 2021 13:36:15 -0700 Subject: [PATCH 3/8] update --- .../azure/identity/OnBehalfOfCredential.java | 44 +++++++++---------- .../identity/OnBehalfOfCredentialBuilder.java | 15 +++++++ .../azure/identity/UserAssertionScope.java | 7 ++- .../implementation/IdentityClient.java | 4 +- .../implementation/IdentityClientOptions.java | 28 ++++++++++-- 5 files changed, 68 insertions(+), 30 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java index 2afcf642d4e5..f1fab89941f5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java @@ -13,39 +13,39 @@ import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; +/** + * An AAD credential that acquires a token with a client secret and user assertion for an AAD application + * on behalf of a user principal. + */ public class OnBehalfOfCredential implements TokenCredential { - private String clientId; - private String tenantId; - private String clientSecret; - private IdentityClientOptions options; - private volatile HashMap userAssertionClientMap; + private IdentityClient identityClient; private final ClientLogger logger = new ClientLogger(OnBehalfOfCredential.class); + /** + * Creates OnBehalfOfCredential with the specified AAD application details and client options. + * + * @param tenantId the tenant ID of the application + * @param clientId the client ID of the application + * @param clientSecret the secret value of the AAD application. + * @param identityClientOptions the options for configuring the identity client + */ public OnBehalfOfCredential(String clientId, String tenantId, String clientSecret, - IdentityClientOptions options) { - this.clientId = clientId; - this.tenantId = tenantId; - this.clientSecret = clientSecret; - this.options = options; - this.userAssertionClientMap = new HashMap<>(); + IdentityClientOptions identityClientOptions) { + this.identityClient = new IdentityClientBuilder() + .tenantId(tenantId) + .clientId(clientId) + .clientSecret(clientSecret) + .identityClientOptions(identityClientOptions) + .build(); } @Override public Mono getToken(TokenRequestContext request) { return Mono.deferContextual(ctx -> { - UserAssertionScope scope = ctx.get(UserAssertionScope.USER_ASSERTION_SCOPE_KEY); - IdentityClient client = userAssertionClientMap.containsKey(scope) ? userAssertionClientMap.get(scope) - : userAssertionClientMap.put(scope, new IdentityClientBuilder() - .tenantId(tenantId) - .clientId(clientId) - .clientSecret(clientSecret) - .identityClientOptions(options) - .build()); - - return client.authenticateWithConfidentialClientCache(request) + return identityClient.authenticateWithConfidentialClientCache(request) .onErrorResume(t -> Mono.empty()) - .switchIfEmpty(Mono.defer(() -> client.authenticateWithOBO(request, scope.UserAssertion))) + .switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithOBO(request))) .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); }); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java index 83b63d6b1692..f5668a581365 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java @@ -26,4 +26,19 @@ public OnBehalfOfCredentialBuilder tokenCachePersistenceOptions(TokenCachePersis this.identityClientOptions.setTokenCacheOptions(tokenCachePersistenceOptions); return this; } + + /** + * Configure the User Assertion Scope to be used for OnBehalfOf Authentication request. + * + * @param userAssertionScope the user assertion to be used for On behalf Of authentication flow + * @return An updated instance of this builder with the user assertion scope configured. + */ + public OnBehalfOfCredentialBuilder userAssertionScope(UserAssertionScope userAssertionScope) { + this.identityClientOptions.userAssertion(userAssertionScope.getUserAssertion()); + return this; + } + + public OnBehalfOfCredential build() { + return new OnBehalfOfCredential(clientId, tenantId, clientSecret, identityClientOptions); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java index 5681c56f1eea..55742921b1c4 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java @@ -4,8 +4,7 @@ import com.microsoft.aad.msal4j.UserAssertion; public class UserAssertionScope { - public static String USER_ASSERTION_SCOPE_KEY = "USER_ASSERTION_SCOPE_KEY"; - UserAssertion UserAssertion; + private UserAssertion UserAssertion; /** * Initializes a new instance of {@link UserAssertionScope} using the supplied access token. @@ -14,4 +13,8 @@ public class UserAssertionScope { public UserAssertionScope(String accessToken) { UserAssertion = new UserAssertion(accessToken); } + + UserAssertion getUserAssertion() { + return this.UserAssertion; + } } 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 e794e2235d54..a690137416e5 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 @@ -517,11 +517,11 @@ public Mono authenticateWithAzurePowerShell(TokenRequestContext req * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateWithOBO(TokenRequestContext request, UserAssertion userAssertion) { + public Mono authenticateWithOBO(TokenRequestContext request) { return confidentialClientApplicationAccessor.getValue() .flatMap(confidentialClient -> Mono.fromFuture(() -> confidentialClient.acquireToken(OnBehalfOfParameters - .builder(new HashSet<>(request.getScopes()), userAssertion) + .builder(new HashSet<>(request.getScopes()), options.getUserAssertion()) .build())) .map(MsalToken::new)); } 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 97a1ac2cbe7b..26916c7ab265 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 @@ -7,11 +7,9 @@ import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; import com.azure.core.util.Configuration; -import com.azure.identity.AuthenticationRecord; -import com.azure.identity.AzureAuthorityHosts; -import com.azure.identity.RegionalAuthority; -import com.azure.identity.TokenCachePersistenceOptions; +import com.azure.identity.*; import com.azure.identity.implementation.util.ValidationUtil; +import com.microsoft.aad.msal4j.UserAssertion; import java.time.Duration; import java.util.concurrent.ExecutorService; @@ -39,6 +37,7 @@ public final class IdentityClientOptions { private TokenCachePersistenceOptions tokenCachePersistenceOptions; private boolean cp1Disabled; private RegionalAuthority regionalAuthority; + private UserAssertion userAssertion; /** * Creates an instance of IdentityClientOptions with default settings. @@ -328,4 +327,25 @@ public IdentityClientOptions setRegionalAuthority(RegionalAuthority regionalAuth public RegionalAuthority getRegionalAuthority() { return regionalAuthority; } + + + /** + * Configure the User Assertion Scope to be used for OnBehalfOf Authentication request. + * + * @param userAssertion the user assertion to be used for On behalf Of authentication flow + * @return the updated identity client options + */ + public IdentityClientOptions userAssertion(UserAssertion userAssertion) { + this.userAssertion = userAssertion; + return this; + } + + /** + * Get the configured {@link UserAssertion} + * + * @return the configured user assertion scope + */ + public UserAssertion getUserAssertion() { + return this.userAssertion; + } } From 2c3f2bfdfc2c8c369c6c40a9054ead1d1f359704 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Wed, 1 Sep 2021 14:51:32 -0700 Subject: [PATCH 4/8] update --- .../com/azure/identity/OnBehalfOfCredentialBuilder.java | 6 +++--- .../identity/implementation/IdentityClientOptions.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java index f5668a581365..aea5c4f3e97c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java @@ -30,11 +30,11 @@ public OnBehalfOfCredentialBuilder tokenCachePersistenceOptions(TokenCachePersis /** * Configure the User Assertion Scope to be used for OnBehalfOf Authentication request. * - * @param userAssertionScope the user assertion to be used for On behalf Of authentication flow + * @param userAssertion the user assertion access token to be used for On behalf Of authentication flow * @return An updated instance of this builder with the user assertion scope configured. */ - public OnBehalfOfCredentialBuilder userAssertionScope(UserAssertionScope userAssertionScope) { - this.identityClientOptions.userAssertion(userAssertionScope.getUserAssertion()); + public OnBehalfOfCredentialBuilder userAssertion(String userAssertion) { + this.identityClientOptions.userAssertion(); return this; } 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 26916c7ab265..1600010b2f9c 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 @@ -332,11 +332,11 @@ public RegionalAuthority getRegionalAuthority() { /** * Configure the User Assertion Scope to be used for OnBehalfOf Authentication request. * - * @param userAssertion the user assertion to be used for On behalf Of authentication flow + * @param userAssertion the user assertion access token to be used for On behalf Of authentication flow * @return the updated identity client options */ - public IdentityClientOptions userAssertion(UserAssertion userAssertion) { - this.userAssertion = userAssertion; + public IdentityClientOptions userAssertion(String userAssertion) { + this.userAssertion = new UserAssertion(userAssertion); return this; } From 00ea675d4137e1fd323980e464de36d7cba800e1 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Wed, 1 Sep 2021 14:59:46 -0700 Subject: [PATCH 5/8] update --- .../credential/AccessTokenExperimental.java | 42 ------------------- .../implementation/AccessTokenCacheImpl.java | 21 +++++----- .../azure/identity/OnBehalfOfCredential.java | 6 +-- .../identity/OnBehalfOfCredentialBuilder.java | 5 ++- .../azure/identity/UserAssertionScope.java | 20 --------- 5 files changed, 17 insertions(+), 77 deletions(-) delete mode 100644 sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java deleted file mode 100644 index 42792e65fb97..000000000000 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/credential/AccessTokenExperimental.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.azure.core.experimental.credential; - -import com.azure.core.credential.AccessToken; - -import java.time.OffsetDateTime; - -public class AccessTokenExperimental extends AccessToken { - private OffsetDateTime refreshOn; - - /** - * Creates an access token instance. - * - * @param token the token string. - * @param expiresAt the expiration time. - */ - public AccessTokenExperimental(String token, OffsetDateTime expiresAt) { - super(token, expiresAt); - if (expiresAt.compareTo(OffsetDateTime.MIN.plusMinutes(5)) > 0) { - refreshOn = expiresAt.minusMinutes(5); - } else { - refreshOn = expiresAt; - } - } - - /** - * Creates an access token instance. - * - * @param token the token string. - * @param expiresAt the expiration time. - */ - public AccessTokenExperimental(String token, OffsetDateTime expiresAt, OffsetDateTime refreshOn) { - super(token, expiresAt); - this.refreshOn = refreshOn; - } - - /** - * @return the time when the token should be refreshed, in UTC. - */ - public OffsetDateTime getRefreshOn() { - return refreshOn; - } -} diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java index a6646ffd894b..a976dd643844 100644 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java @@ -6,7 +6,6 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; -import com.azure.core.experimental.credential.AccessTokenExperimental; import com.azure.core.util.logging.ClientLogger; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,7 +27,7 @@ public class AccessTokenCacheImpl { // The delay after a refresh to attempt another token refresh private static final Duration REFRESH_DELAY = Duration.ofSeconds(30); // the offset before token expiry to attempt proactive token refresh -// private static final Duration REFRESH_OFFSET = Duration.ofMinutes(5); + private static final Duration REFRESH_OFFSET = Duration.ofMinutes(5); private volatile AccessToken cache; private volatile OffsetDateTime nextTokenRefresh = OffsetDateTime.now(); private final AtomicReference> wip; @@ -46,8 +45,8 @@ public AccessTokenCacheImpl(TokenCredential tokenCredential) { Objects.requireNonNull(tokenCredential, "The token credential cannot be null"); this.wip = new AtomicReference<>(); this.tokenCredential = tokenCredential; - this.shouldRefresh = accessToken -> OffsetDateTime.now().isAfter(accessToken instanceof AccessTokenExperimental - ? ((AccessTokenExperimental) accessToken).getRefreshOn() : accessToken.getExpiresAt().minusMinutes(5)); + this.shouldRefresh = accessToken -> OffsetDateTime.now() + .isAfter(accessToken.getExpiresAt().minus(REFRESH_OFFSET)); } /** @@ -92,7 +91,7 @@ private Supplier> retrieveToken(TokenRequestContext } else { // wait for timeout, then refresh tokenRefresh = Mono.defer(tokenSupplier) - .delaySubscription(Duration.between(now, nextTokenRefresh)); + .delaySubscription(Duration.between(now, nextTokenRefresh)); } // cache doesn't exist or expired, no fallback fallback = Mono.empty(); @@ -109,10 +108,10 @@ private Supplier> retrieveToken(TokenRequestContext fallback = Mono.just(cache); } return tokenRefresh - .materialize() - .flatMap(processTokenRefreshResult(sinksOne, now, fallback)) - .doOnError(sinksOne::tryEmitError) - .doFinally(ignored -> wip.set(null)); + .materialize() + .flatMap(processTokenRefreshResult(sinksOne, now, fallback)) + .doOnError(sinksOne::tryEmitError) + .doFinally(ignored -> wip.set(null)); } else { return Mono.empty(); } @@ -125,8 +124,8 @@ private Supplier> retrieveToken(TokenRequestContext private boolean checkIfWeShouldForceRefresh(TokenRequestContext tokenRequestContext) { return !(this.tokenRequestContext != null && (this.tokenRequestContext.getClaims() == null ? tokenRequestContext.getClaims() == null - : (tokenRequestContext.getClaims() == null ? false - : tokenRequestContext.getClaims().equals(this.tokenRequestContext.getClaims()))) + : (tokenRequestContext.getClaims() == null ? false + : tokenRequestContext.getClaims().equals(this.tokenRequestContext.getClaims()))) && this.tokenRequestContext.getScopes().equals(tokenRequestContext.getScopes())); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java index f1fab89941f5..eb62f9680865 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.azure.identity; import com.azure.core.credential.AccessToken; @@ -10,9 +13,6 @@ import com.azure.identity.implementation.util.LoggingUtil; import reactor.core.publisher.Mono; -import java.util.HashMap; -import java.util.concurrent.ConcurrentHashMap; - /** * An AAD credential that acquires a token with a client secret and user assertion for an AAD application * on behalf of a user principal. diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java index aea5c4f3e97c..4fc737d56467 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.azure.identity; public class OnBehalfOfCredentialBuilder extends AadCredentialBuilderBase { @@ -34,7 +37,7 @@ public OnBehalfOfCredentialBuilder tokenCachePersistenceOptions(TokenCachePersis * @return An updated instance of this builder with the user assertion scope configured. */ public OnBehalfOfCredentialBuilder userAssertion(String userAssertion) { - this.identityClientOptions.userAssertion(); + this.identityClientOptions.userAssertion(userAssertion); return this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java deleted file mode 100644 index 55742921b1c4..000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScope.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.azure.identity; - -import com.azure.identity.implementation.IdentityClient; -import com.microsoft.aad.msal4j.UserAssertion; - -public class UserAssertionScope { - private UserAssertion UserAssertion; - - /** - * Initializes a new instance of {@link UserAssertionScope} using the supplied access token. - * @param accessToken The access token that will be used by {@link OnBehalfOfCredential} as the user assertion when requesting On-Behalf-Of tokens. - */ - public UserAssertionScope(String accessToken) { - UserAssertion = new UserAssertion(accessToken); - } - - UserAssertion getUserAssertion() { - return this.UserAssertion; - } -} From 720392c6b65f08604f78ff4ab6691f32d54319fa Mon Sep 17 00:00:00 2001 From: g2vinay Date: Wed, 1 Sep 2021 15:27:03 -0700 Subject: [PATCH 6/8] update --- .../implementation/AccessTokenCacheImpl.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java index a976dd643844..a988dbb94393 100644 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/implementation/AccessTokenCacheImpl.java @@ -91,7 +91,7 @@ private Supplier> retrieveToken(TokenRequestContext } else { // wait for timeout, then refresh tokenRefresh = Mono.defer(tokenSupplier) - .delaySubscription(Duration.between(now, nextTokenRefresh)); + .delaySubscription(Duration.between(now, nextTokenRefresh)); } // cache doesn't exist or expired, no fallback fallback = Mono.empty(); @@ -108,10 +108,10 @@ private Supplier> retrieveToken(TokenRequestContext fallback = Mono.just(cache); } return tokenRefresh - .materialize() - .flatMap(processTokenRefreshResult(sinksOne, now, fallback)) - .doOnError(sinksOne::tryEmitError) - .doFinally(ignored -> wip.set(null)); + .materialize() + .flatMap(processTokenRefreshResult(sinksOne, now, fallback)) + .doOnError(sinksOne::tryEmitError) + .doFinally(ignored -> wip.set(null)); } else { return Mono.empty(); } @@ -124,8 +124,8 @@ private Supplier> retrieveToken(TokenRequestContext private boolean checkIfWeShouldForceRefresh(TokenRequestContext tokenRequestContext) { return !(this.tokenRequestContext != null && (this.tokenRequestContext.getClaims() == null ? tokenRequestContext.getClaims() == null - : (tokenRequestContext.getClaims() == null ? false - : tokenRequestContext.getClaims().equals(this.tokenRequestContext.getClaims()))) + : (tokenRequestContext.getClaims() == null ? false + : tokenRequestContext.getClaims().equals(this.tokenRequestContext.getClaims()))) && this.tokenRequestContext.getScopes().equals(tokenRequestContext.getScopes())); } From 7c7879b8b920615c41620c14786de3a2309d14b7 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Tue, 7 Sep 2021 07:54:20 -0700 Subject: [PATCH 7/8] update --- .../identity/ClientAssertionCredential.java | 25 ++++++++++++++ .../identity/ManagedIdentityCredential.java | 29 +++++++++++----- .../implementation/IdentityClient.java | 33 +++++++++++++++++-- .../implementation/IdentityClientBuilder.java | 14 +++++++- .../IdentityClientIntegrationTests.java | 10 +++--- 5 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java new file mode 100644 index 000000000000..5573c720238c --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java @@ -0,0 +1,25 @@ +package com.azure.identity; + +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenRequestContext; +import com.azure.identity.implementation.IdentityClient; +import reactor.core.publisher.Mono; + +public class ClientAssertionCredential extends ManagedIdentityServiceCredential { + + /** + * Creates an instance of ManagedIdentityServiceCredential. + * + * @param clientId the client id of user assigned or system assigned identity. + * @param identityClient the identity client to acquire a token with. + */ + ClientAssertionCredential(String clientId, IdentityClient identityClient) { + super(clientId, identityClient, "AZURE AKS TOKEN EXCHANGE"); + } + + @Override + public Mono authenticate(TokenRequestContext request) { + System.out.printf("Attemtping Auth"); + return identityClient.authenticatewithExchangeToken(request); + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index d5129fb369c2..527611c1d44d 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -25,6 +25,7 @@ public final class ManagedIdentityCredential implements TokenCredential { static final String PROPERTY_IMDS_ENDPOINT = "IMDS_ENDPOINT"; static final String PROPERTY_IDENTITY_SERVER_THUMBPRINT = "IDENTITY_SERVER_THUMBPRINT"; + static final String TOKEN_FILE_PATH = "TOKEN_FILE_PATH"; /** @@ -33,28 +34,38 @@ public final class ManagedIdentityCredential implements TokenCredential { * @param identityClientOptions the options for configuring the identity client. */ ManagedIdentityCredential(String clientId, IdentityClientOptions identityClientOptions) { - IdentityClient identityClient = new IdentityClientBuilder() + IdentityClientBuilder clientBuilder = new IdentityClientBuilder() .clientId(clientId) - .identityClientOptions(identityClientOptions) - .build(); + .identityClientOptions(identityClientOptions); + Configuration configuration = Configuration.getGlobalConfiguration().clone(); if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT)) { - managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, identityClient); + managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, clientBuilder.build()); } else if (configuration.contains(Configuration.PROPERTY_IDENTITY_ENDPOINT)) { if (configuration.contains(Configuration.PROPERTY_IDENTITY_HEADER)) { if (configuration.get(PROPERTY_IDENTITY_SERVER_THUMBPRINT) != null) { - managedIdentityServiceCredential = new ServiceFabricMsiCredential(clientId, identityClient); + managedIdentityServiceCredential = new ServiceFabricMsiCredential(clientId, clientBuilder.build()); } else { - managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, identityClient); + managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder.build()); } } else if (configuration.get(PROPERTY_IMDS_ENDPOINT) != null) { - managedIdentityServiceCredential = new ArcIdentityCredential(clientId, identityClient); + managedIdentityServiceCredential = new ArcIdentityCredential(clientId, clientBuilder.build()); } else { - managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, identityClient); + managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder.build()); } + } else if (configuration.contains(Configuration.PROPERTY_AZURE_CLIENT_ID) + && configuration.contains(Configuration.PROPERTY_AZURE_TENANT_ID) + && configuration.get(TOKEN_FILE_PATH) != null) { + clientBuilder.tenantId(configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID)); + clientBuilder.clientAssertionPath(configuration.get(TOKEN_FILE_PATH)); + System.out.printf("Located TOKEN FIle Path %s, Tenant %s, Client %s", configuration.get(TOKEN_FILE_PATH), + configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID), + configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID)); + managedIdentityServiceCredential = new ClientAssertionCredential(clientId,clientBuilder.build()); + } else { - managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, identityClient); + managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder.build()); } LoggingUtil.logAvailableEnvironmentVariables(logger, configuration); } 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 0f02ac01ee92..cda580ead533 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 @@ -122,6 +122,7 @@ public class IdentityClient { private final String tenantId; private final String clientId; private final String clientSecret; + private final String clientAssertionFilePath; private final InputStream certificate; private final String certificatePath; private final String certificatePassword; @@ -143,8 +144,8 @@ public class IdentityClient { * @param options the options configuring the client. */ IdentityClient(String tenantId, String clientId, String clientSecret, String certificatePath, - InputStream certificate, String certificatePassword, boolean isSharedTokenCacheCredential, - IdentityClientOptions options) { + String clientAssertionFilePath, InputStream certificate, String certificatePassword, + boolean isSharedTokenCacheCredential, IdentityClientOptions options) { if (tenantId == null) { tenantId = "organizations"; } @@ -154,6 +155,7 @@ public class IdentityClient { this.tenantId = tenantId; this.clientId = clientId; this.clientSecret = clientSecret; + this.clientAssertionFilePath = clientAssertionFilePath; this.certificatePath = certificatePath; this.certificate = certificate; this.certificatePassword = certificatePassword; @@ -205,6 +207,16 @@ private Mono getConfidentialClientApplication() { return Mono.error(logger.logExceptionAsError(new RuntimeException( "Failed to parse the certificate for the credential: " + e.getMessage(), e))); } + } else if (clientAssertionFilePath != null) { + System.out.printf("Received %s assertion token file path.", clientAssertionFilePath); + try { + credential = ClientCredentialFactory + .createFromClientAssertion(parseClientAssertion(clientAssertionFilePath)); + } catch (IOException e) { + return Mono.error(logger.logExceptionAsError(new RuntimeException( + "Failed to parse the client assertion from the provided file: " + clientAssertionFilePath + + ". " + e.getMessage(), e))); + } } else { return Mono.error(logger.logExceptionAsError( new IllegalArgumentException("Must provide client secret or client certificate path"))); @@ -256,6 +268,13 @@ private Mono getConfidentialClientApplication() { }); } + private String parseClientAssertion(String clientAssertionFilePath) throws IOException { + System.out.printf("Parsing token"); + byte[] encoded = Files.readAllBytes(Paths.get(clientAssertionFilePath)); + System.out.printf("Parsed token: %s", new String(encoded, StandardCharsets.UTF_8)); + return new String(encoded, StandardCharsets.UTF_8); + } + private Mono getPublicClientApplication(boolean sharedTokenCacheCredential) { return Mono.defer(() -> { if (clientId == null) { @@ -994,6 +1013,16 @@ public Mono authenticateToArcManagedIdentityEndpoint(String identit }); } + /** + * Asynchronously acquire a token from the Azure Arc Managed Service Identity endpoint. + * + * @param request the details of the token request + * @return a Publisher that emits an AccessToken + */ + public Mono authenticatewithExchangeToken(TokenRequestContext request) { + return authenticateWithConfidentialClient(request); + } + /** * Asynchronously acquire a token from the Azure Service Fabric Managed Service Identity endpoint. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java index c7c25071d66c..3705623ce68f 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java @@ -17,6 +17,7 @@ public final class IdentityClientBuilder { private String tenantId; private String clientId; private String clientSecret; + private String clientAssertionPath; private String certificatePath; private InputStream certificate; private String certificatePassword; @@ -63,6 +64,17 @@ public IdentityClientBuilder certificatePath(String certificatePath) { return this; } + /** + * Sets the client certificate for the client. + * + * @param clientAssertionPath the path to the file containing client assertion. + * @return the IdentityClientBuilder itself + */ + public IdentityClientBuilder clientAssertionPath(String clientAssertionPath) { + this.clientAssertionPath = clientAssertionPath; + return this; + } + /** * Sets the client certificate for the client. * @@ -110,7 +122,7 @@ public IdentityClientBuilder sharedTokenCacheCredential(boolean isSharedTokenCac * @return a {@link IdentityClient} with the current configurations. */ public IdentityClient build() { - return new IdentityClient(tenantId, clientId, clientSecret, certificatePath, certificate, + return new IdentityClient(tenantId, clientId, clientSecret, certificatePath, clientAssertionPath, certificate, certificatePassword, sharedTokenCacheCred, identityClientOptions); } } 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 4d6ea9b4ede7..4955fb796fe9 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java @@ -23,7 +23,7 @@ public class IdentityClientIntegrationTests { @Ignore("Integration tests") public void clientSecretCanGetToken() { - IdentityClient client = new IdentityClient(System.getenv(AZURE_TENANT_ID), System.getenv(AZURE_CLIENT_ID), System.getenv(AZURE_CLIENT_SECRET), null, null, null, false, new IdentityClientOptions()); + IdentityClient client = new IdentityClient(System.getenv(AZURE_TENANT_ID), System.getenv(AZURE_CLIENT_ID), System.getenv(AZURE_CLIENT_SECRET), null, null, null, null, false, new IdentityClientOptions()); StepVerifier.create(client.authenticateWithConfidentialClient(request)) .expectNextMatches(token -> token.getToken() != null && token.getExpiresAt() != null @@ -38,7 +38,7 @@ public void clientSecretCanGetToken() { @Ignore("Integration tests") public void deviceCodeCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithDeviceCode(request, deviceCode -> { System.out.println(deviceCode.getMessage()); try { @@ -60,7 +60,7 @@ public void deviceCodeCanGetToken() { @Ignore("Integration tests") public void browserCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithBrowserInteraction(request, 8765, null, null).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); @@ -75,7 +75,7 @@ public void browserCanGetToken() { @Ignore("Integration tests") public void usernamePasswordCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithUsernamePassword(request, System.getenv("username"), System.getenv("password")).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); @@ -90,7 +90,7 @@ public void usernamePasswordCanGetToken() { @Ignore("Integration tests") public void authCodeCanGetToken() throws Exception { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, false, new IdentityClientOptions()); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions()); MsalToken token = client.authenticateWithAuthorizationCode(request, System.getenv("AZURE_AUTH_CODE"), new URI("http://localhost:8000")).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); From 3506fb30186b2d29b0015e0544d85ffce325625f Mon Sep 17 00:00:00 2001 From: g2vinay Date: Tue, 7 Sep 2021 09:21:19 -0700 Subject: [PATCH 8/8] updates --- .../identity/ClientAssertionCredential.java | 12 ++- .../identity/ManagedIdentityCredential.java | 6 +- .../azure/identity/OnBehalfOfCredential.java | 25 +++--- .../identity/OnBehalfOfCredentialBuilder.java | 82 ++++++++++++++++++- .../identity/UserAssertionScopeOptions.java | 4 - .../implementation/IdentityClient.java | 32 ++++++-- .../implementation/IdentityClientBuilder.java | 15 +++- .../implementation/IdentityClientOptions.java | 5 +- .../implementation/SynchronizedAccessor.java | 5 ++ .../AzureApplicationCredentialTest.java | 2 +- .../ClientCertificateCredentialTest.java | 18 ++-- .../identity/ClientSecretCredentialTest.java | 4 +- .../identity/DefaultAzureCredentialTest.java | 2 +- .../IdentityClientIntegrationTests.java | 10 +-- 14 files changed, 171 insertions(+), 51 deletions(-) delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java index 5573c720238c..7189ed7b4c51 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientAssertionCredential.java @@ -1,14 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.azure.identity; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenRequestContext; import com.azure.identity.implementation.IdentityClient; + import reactor.core.publisher.Mono; -public class ClientAssertionCredential extends ManagedIdentityServiceCredential { +/** + * Authenticates a service principal with AAD using a client assertion. + */ +class ClientAssertionCredential extends ManagedIdentityServiceCredential { /** - * Creates an instance of ManagedIdentityServiceCredential. + * Creates an instance of ClientAssertionCredential. * * @param clientId the client id of user assigned or system assigned identity. * @param identityClient the identity client to acquire a token with. @@ -19,7 +26,6 @@ public class ClientAssertionCredential extends ManagedIdentityServiceCredential @Override public Mono authenticate(TokenRequestContext request) { - System.out.printf("Attemtping Auth"); return identityClient.authenticatewithExchangeToken(request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index 527611c1d44d..cd4789c7fd01 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -9,7 +9,6 @@ import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.Configuration; import com.azure.core.util.logging.ClientLogger; -import com.azure.identity.implementation.IdentityClient; import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; import com.azure.identity.implementation.util.LoggingUtil; @@ -59,10 +58,7 @@ public final class ManagedIdentityCredential implements TokenCredential { && configuration.get(TOKEN_FILE_PATH) != null) { clientBuilder.tenantId(configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID)); clientBuilder.clientAssertionPath(configuration.get(TOKEN_FILE_PATH)); - System.out.printf("Located TOKEN FIle Path %s, Tenant %s, Client %s", configuration.get(TOKEN_FILE_PATH), - configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID), - configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID)); - managedIdentityServiceCredential = new ClientAssertionCredential(clientId,clientBuilder.build()); + managedIdentityServiceCredential = new ClientAssertionCredential(clientId, clientBuilder.build()); } else { managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder.build()); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java index eb62f9680865..4ea37b537077 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredential.java @@ -13,12 +13,14 @@ import com.azure.identity.implementation.util.LoggingUtil; import reactor.core.publisher.Mono; +import java.time.Duration; + /** * An AAD credential that acquires a token with a client secret and user assertion for an AAD application * on behalf of a user principal. */ public class OnBehalfOfCredential implements TokenCredential { - private IdentityClient identityClient; + private final IdentityClient identityClient; private final ClientLogger logger = new ClientLogger(OnBehalfOfCredential.class); @@ -28,26 +30,29 @@ public class OnBehalfOfCredential implements TokenCredential { * @param tenantId the tenant ID of the application * @param clientId the client ID of the application * @param clientSecret the secret value of the AAD application. + * @param certificatePath the PEM file or PFX file containing the certificate + * @param certificatePassword the password protecting the PFX file * @param identityClientOptions the options for configuring the identity client */ - public OnBehalfOfCredential(String clientId, String tenantId, String clientSecret, - IdentityClientOptions identityClientOptions) { + public OnBehalfOfCredential(String clientId, String tenantId, String clientSecret, String certificatePath, + String certificatePassword, IdentityClientOptions identityClientOptions) { this.identityClient = new IdentityClientBuilder() .tenantId(tenantId) .clientId(clientId) .clientSecret(clientSecret) + .certificatePath(certificatePath) + .certificatePassword(certificatePassword) .identityClientOptions(identityClientOptions) + .confidentialClientCacheTimeout(Duration.ofMinutes(5)) .build(); } @Override public Mono getToken(TokenRequestContext request) { - return Mono.deferContextual(ctx -> { - return identityClient.authenticateWithConfidentialClientCache(request) - .onErrorResume(t -> Mono.empty()) - .switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithOBO(request))) - .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) - .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); - }); + return Mono.deferContextual(ctx -> identityClient.authenticateWithConfidentialClientCache(request) + .onErrorResume(t -> Mono.empty()) + .switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithOBO(request))) + .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) + .doOnError(error -> LoggingUtil.logTokenError(logger, request, error))); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java index 4fc737d56467..c988f1b88628 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/OnBehalfOfCredentialBuilder.java @@ -3,8 +3,21 @@ package com.azure.identity; +import com.azure.core.util.logging.ClientLogger; +import com.azure.identity.implementation.util.ValidationUtil; + +import java.util.HashMap; + +/** + * Fluent credential builder for instantiating a {@link OnBehalfOfCredential}. + * + * @see OnBehalfOfCredential + */ public class OnBehalfOfCredentialBuilder extends AadCredentialBuilderBase { private String clientSecret; + private String clientCertificatePath; + private String clientCertificatePassword; + private final ClientLogger logger = new ClientLogger(OnBehalfOfCredentialBuilder.class); /** * Sets the client secret for the authentication. @@ -30,6 +43,46 @@ public OnBehalfOfCredentialBuilder tokenCachePersistenceOptions(TokenCachePersis return this; } + /** + * Sets the path and password of the PFX certificate for authenticating to AAD. + * + * @param certificatePath the password protected PFX file containing the certificate + * @param clientCertificatePassword the password protecting the PFX file + * @return An updated instance of this builder. + */ + public OnBehalfOfCredentialBuilder pfxCertificate(String certificatePath, + String clientCertificatePassword) { + this.clientCertificatePath = certificatePath; + this.clientCertificatePassword = clientCertificatePassword; + return this; + } + + /** + * Specifies if the x5c claim (public key of the certificate) should be sent as part of the authentication request + * and enable subject name / issuer based authentication. The default value is false. + * + * @param sendCertificateChain the flag to indicate if certificate chain should be sent as part of authentication + * request. + * @return An updated instance of this builder. + */ + public OnBehalfOfCredentialBuilder sendCertificateChain(boolean sendCertificateChain) { + this.identityClientOptions.setIncludeX5c(sendCertificateChain); + return this; + } + + /** + * Specifies either the specific regional authority, or use {@link RegionalAuthority#AUTO_DISCOVER_REGION} to + * attempt to auto-detect the region. If unset, a non-regional authority will be used. This argument should be used + * only by applications deployed to Azure VMs. + * + * @param regionalAuthority the regional authority + * @return An updated instance of this builder with the regional authority configured. + */ + public OnBehalfOfCredentialBuilder regionalAuthority(RegionalAuthority regionalAuthority) { + this.identityClientOptions.setRegionalAuthority(regionalAuthority); + return this; + } + /** * Configure the User Assertion Scope to be used for OnBehalfOf Authentication request. * @@ -41,7 +94,34 @@ public OnBehalfOfCredentialBuilder userAssertion(String userAssertion) { return this; } + /** + * Creates a new {@link OnBehalfOfCredential} with the current configurations. + * + * @return a {@link OnBehalfOfCredential} with the current configurations. + * @throws IllegalArgumentException if eiter both the client secret and certificate are configured or none of them + * are configured. + */ public OnBehalfOfCredential build() { - return new OnBehalfOfCredential(clientId, tenantId, clientSecret, identityClientOptions); + ValidationUtil.validate(getClass().getSimpleName(), new HashMap() { + { + put("clientId", clientId); + put("tenantId", tenantId); + } + }); + + if (clientSecret == null && clientCertificatePath == null) { + throw logger.logExceptionAsWarning(new IllegalArgumentException("Atleast client secret or certificate " + + "path should provided in OnBhealfOfCredentialBuilder. Only one of them should " + + "be provided.")); + } + + if (clientCertificatePath != null && clientSecret != null) { + throw logger.logExceptionAsWarning(new IllegalArgumentException("Both client secret and certificate " + + "path are provided in OnBhealfCredentialBuilder. Only one of them should " + + "be provided.")); + } + + return new OnBehalfOfCredential(clientId, tenantId, clientSecret, clientCertificatePath, + clientCertificatePassword, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java deleted file mode 100644 index 1a3284006a95..000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UserAssertionScopeOptions.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.azure.identity; - -public class UserAssertionScopeOptions { -} 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 af67da8b1f21..bb87cef1a3f4 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 @@ -31,7 +31,23 @@ import com.azure.identity.implementation.util.IdentitySslUtil; import com.azure.identity.implementation.util.ScopeUtil; import com.fasterxml.jackson.databind.JsonNode; -import com.microsoft.aad.msal4j.*; +import com.microsoft.aad.msal4j.AuthorizationCodeParameters; +import com.microsoft.aad.msal4j.ClaimsRequest; +import com.microsoft.aad.msal4j.ClientCredentialFactory; +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.IAuthenticationResult; +import com.microsoft.aad.msal4j.IClientCredential; +import com.microsoft.aad.msal4j.InteractiveRequestParameters; +import com.microsoft.aad.msal4j.MsalInteractionRequiredException; +import com.microsoft.aad.msal4j.OnBehalfOfParameters; +import com.microsoft.aad.msal4j.Prompt; +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.sun.jna.Platform; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -126,11 +142,13 @@ public class IdentityClient { * @param certificatePassword the password protecting the PFX certificate. * @param isSharedTokenCacheCredential Indicate whether the credential is * {@link com.azure.identity.SharedTokenCacheCredential} or not. + * @param confidentialClientCacheTimeout the cache time out to use for confidential client. * @param options the options configuring the client. */ IdentityClient(String tenantId, String clientId, String clientSecret, String certificatePath, String clientAssertionFilePath, InputStream certificate, String certificatePassword, - boolean isSharedTokenCacheCredential, IdentityClientOptions options) { + boolean isSharedTokenCacheCredential, Duration confidentialClientCacheTimeout, + IdentityClientOptions options) { if (tenantId == null) { tenantId = "organizations"; } @@ -146,11 +164,12 @@ public class IdentityClient { this.certificatePassword = certificatePassword; this.options = options; - this.publicClientApplicationAccessor = new SynchronizedAccessor(() -> + this.publicClientApplicationAccessor = new SynchronizedAccessor<>(() -> getPublicClientApplication(isSharedTokenCacheCredential)); - this.confidentialClientApplicationAccessor = new SynchronizedAccessor(() -> - getConfidentialClientApplication()); + this.confidentialClientApplicationAccessor = confidentialClientCacheTimeout == null + ? new SynchronizedAccessor<>(() -> getConfidentialClientApplication()) + : new SynchronizedAccessor<>(() -> getConfidentialClientApplication(), confidentialClientCacheTimeout); } private Mono getConfidentialClientApplication() { @@ -193,7 +212,6 @@ private Mono getConfidentialClientApplication() { "Failed to parse the certificate for the credential: " + e.getMessage(), e))); } } else if (clientAssertionFilePath != null) { - System.out.printf("Received %s assertion token file path.", clientAssertionFilePath); try { credential = ClientCredentialFactory .createFromClientAssertion(parseClientAssertion(clientAssertionFilePath)); @@ -254,9 +272,7 @@ private Mono getConfidentialClientApplication() { } private String parseClientAssertion(String clientAssertionFilePath) throws IOException { - System.out.printf("Parsing token"); byte[] encoded = Files.readAllBytes(Paths.get(clientAssertionFilePath)); - System.out.printf("Parsed token: %s", new String(encoded, StandardCharsets.UTF_8)); return new String(encoded, StandardCharsets.UTF_8); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java index 3705623ce68f..51192fe87f78 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java @@ -6,6 +6,7 @@ import com.azure.identity.SharedTokenCacheCredential; import java.io.InputStream; +import java.time.Duration; /** * Fluent client builder for instantiating an {@link IdentityClient}. @@ -22,6 +23,7 @@ public final class IdentityClientBuilder { private InputStream certificate; private String certificatePassword; private boolean sharedTokenCacheCred; + private Duration confidentialClientCacheTimeout; /** * Sets the tenant ID for the client. @@ -118,11 +120,22 @@ public IdentityClientBuilder sharedTokenCacheCredential(boolean isSharedTokenCac return this; } + /** + * Configure the time out to use re-use confidential client for. Post time out, a new instance of client is created. + * + * @param confidentialClientCacheTimeout the time out to use for confidential client cache. + * @return the updated IdentityClientBuilder. + */ + public IdentityClientBuilder confidentialClientCacheTimeout(Duration confidentialClientCacheTimeout) { + this.confidentialClientCacheTimeout = confidentialClientCacheTimeout; + return this; + } + /** * @return a {@link IdentityClient} with the current configurations. */ public IdentityClient build() { return new IdentityClient(tenantId, clientId, clientSecret, certificatePath, clientAssertionPath, certificate, - certificatePassword, sharedTokenCacheCred, identityClientOptions); + certificatePassword, sharedTokenCacheCred, confidentialClientCacheTimeout, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index a760dd071414..235b5c2da6cd 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 @@ -7,7 +7,10 @@ import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; import com.azure.core.util.Configuration; -import com.azure.identity.*; +import com.azure.identity.AzureAuthorityHosts; +import com.azure.identity.AuthenticationRecord; +import com.azure.identity.RegionalAuthority; +import com.azure.identity.TokenCachePersistenceOptions; import com.azure.identity.implementation.util.ValidationUtil; import com.microsoft.aad.msal4j.UserAssertion; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SynchronizedAccessor.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SynchronizedAccessor.java index c63bdf444ca3..5754a857ec6e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SynchronizedAccessor.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SynchronizedAccessor.java @@ -5,6 +5,7 @@ import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.function.Supplier; /** @@ -19,6 +20,10 @@ public SynchronizedAccessor(Supplier> supplier) { monoCache = supplier.get().cache(); } + public SynchronizedAccessor(Supplier> supplier, Duration cacheTimeout) { + monoCache = supplier.get().cache(cacheTimeout); + } + /** * Get the value from the configured supplier. * diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java index 56c0031507ae..73b391f9c394 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java @@ -50,7 +50,7 @@ public void testUseEnvironmentCredential() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(identityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(secret), isNull(), isNull(), isNull(), eq(false), any()).thenReturn(identityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(secret), isNull(), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(identityClient); // test AzureApplicationCredential credential = new AzureApplicationCredentialBuilder().build(); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java index 67fe612e58a0..4b181c11677c 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientCertificateCredentialTest.java @@ -57,8 +57,8 @@ public void testValidCertificatePaths() throws Exception { when(pfxIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(pemIdentityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); when(pfxIdentityClient.authenticateWithConfidentialClient(request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt)); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pemPath), isNull(), isNull(), eq(false), any()).thenReturn(pemIdentityClient); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pfxPath), isNull(), eq(pfxPassword), eq(false), any()).thenReturn(pfxIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pemPath), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(pemIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pfxPath), isNull(), isNull(), eq(pfxPassword), eq(false), isNull(), any()).thenReturn(pfxIdentityClient); // test ClientCertificateCredential credential = @@ -95,8 +95,8 @@ public void testValidCertificates() throws Exception { when(pfxIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(pemIdentityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); when(pfxIdentityClient.authenticateWithConfidentialClient(request2)).thenReturn(TestUtils.getMockAccessToken(token2, expiresAt)); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), eq(pemCert), isNull(), eq(false), any()).thenReturn(pemIdentityClient); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), eq(pfxCert), eq(pfxPassword), eq(false), any()).thenReturn(pfxIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), isNull(), eq(pemCert), isNull(), eq(false), isNull(), any()).thenReturn(pemIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), isNull(), eq(pfxCert), eq(pfxPassword), eq(false), isNull(), any()).thenReturn(pfxIdentityClient); // test ClientCertificateCredential credential = @@ -129,8 +129,8 @@ public void testInvalidCertificatePaths() throws Exception { when(pfxIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(pemIdentityClient.authenticateWithConfidentialClient(request1)).thenReturn(Mono.error(new MsalServiceException("bad pem", "BadPem"))); when(pfxIdentityClient.authenticateWithConfidentialClient(request2)).thenReturn(Mono.error(new MsalServiceException("bad pfx", "BadPfx"))); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pemPath), isNull(), isNull(), eq(false), any()).thenReturn(pemIdentityClient); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pfxPath), isNull(), eq(pfxPassword), eq(false), any()).thenReturn(pfxIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pemPath), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(pemIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pfxPath), isNull(), isNull(), eq(pfxPassword), eq(false), isNull(), any()).thenReturn(pfxIdentityClient); // test ClientCertificateCredential credential = @@ -162,8 +162,8 @@ public void testInvalidCertificates() throws Exception { when(pfxIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(pemIdentityClient.authenticateWithConfidentialClient(request1)).thenReturn(Mono.error(new MsalServiceException("bad pem", "BadPem"))); when(pfxIdentityClient.authenticateWithConfidentialClient(request2)).thenReturn(Mono.error(new MsalServiceException("bad pfx", "BadPfx"))); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), eq(pemCert), isNull(), eq(false), any()).thenReturn(pemIdentityClient); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), eq(pfxCert), eq(pfxPassword), eq(false), any()).thenReturn(pfxIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), isNull(), eq(pemCert), isNull(), eq(false), isNull(), any()).thenReturn(pemIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), isNull(), isNull(), eq(pfxCert), eq(pfxPassword), eq(false), isNull(), any()).thenReturn(pfxIdentityClient); // test ClientCertificateCredential credential = @@ -191,7 +191,7 @@ public void testInvalidParameters() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(identityClient.authenticateWithConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pemPath), isNull(), isNull(), eq(false), any()).thenReturn(identityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), isNull(), eq(pemPath), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(identityClient); // test try { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java index b4de4b76d9ee..256b54ee1eef 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java @@ -82,8 +82,8 @@ public void testInvalidSecrets() throws Exception { when(badIdentityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(identityClient.authenticateWithConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); when(badIdentityClient.authenticateWithConfidentialClient(request)).thenReturn(Mono.error(new MsalServiceException("bad secret", "BadSecret"))); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(secret), isNull(), isNull(), isNull(), eq(false), any()).thenReturn(identityClient); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(badSecret), isNull(), isNull(), isNull(), eq(false), any()).thenReturn(badIdentityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(secret), isNull(), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(identityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(badSecret), isNull(), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(badIdentityClient); // test ClientSecretCredential credential = 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 27da40c3023c..edea9c5cfcd7 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 @@ -52,7 +52,7 @@ public void testUseEnvironmentCredential() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty()); when(identityClient.authenticateWithConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); - PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(secret), isNull(), isNull(), isNull(), eq(false), any()).thenReturn(identityClient); + PowerMockito.whenNew(IdentityClient.class).withArguments(eq(TENANT_ID), eq(CLIENT_ID), eq(secret), isNull(), isNull(), isNull(), isNull(), eq(false), isNull(), any()).thenReturn(identityClient); IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class); when(intelliJCredential.getToken(request1)) diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java index 4955fb796fe9..02aebbd052f0 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java @@ -23,7 +23,7 @@ public class IdentityClientIntegrationTests { @Ignore("Integration tests") public void clientSecretCanGetToken() { - IdentityClient client = new IdentityClient(System.getenv(AZURE_TENANT_ID), System.getenv(AZURE_CLIENT_ID), System.getenv(AZURE_CLIENT_SECRET), null, null, null, null, false, new IdentityClientOptions()); + IdentityClient client = new IdentityClient(System.getenv(AZURE_TENANT_ID), System.getenv(AZURE_CLIENT_ID), System.getenv(AZURE_CLIENT_SECRET), null, null, null, null, false, null, new IdentityClientOptions()); StepVerifier.create(client.authenticateWithConfidentialClient(request)) .expectNextMatches(token -> token.getToken() != null && token.getExpiresAt() != null @@ -38,7 +38,7 @@ public void clientSecretCanGetToken() { @Ignore("Integration tests") public void deviceCodeCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, null, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithDeviceCode(request, deviceCode -> { System.out.println(deviceCode.getMessage()); try { @@ -60,7 +60,7 @@ public void deviceCodeCanGetToken() { @Ignore("Integration tests") public void browserCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, null, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithBrowserInteraction(request, 8765, null, null).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); @@ -75,7 +75,7 @@ public void browserCanGetToken() { @Ignore("Integration tests") public void usernamePasswordCanGetToken() { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, null, new IdentityClientOptions().setProxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))); MsalToken token = client.authenticateWithUsernamePassword(request, System.getenv("username"), System.getenv("password")).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); @@ -90,7 +90,7 @@ public void usernamePasswordCanGetToken() { @Ignore("Integration tests") public void authCodeCanGetToken() throws Exception { - IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, new IdentityClientOptions()); + IdentityClient client = new IdentityClient("common", System.getenv(AZURE_CLIENT_ID), null, null, null, null, null, false, null, new IdentityClientOptions()); MsalToken token = client.authenticateWithAuthorizationCode(request, System.getenv("AZURE_AUTH_CODE"), new URI("http://localhost:8000")).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken());