From 929dba3ec4b4be4b7541e376bd84ac0b717ae6a2 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Mon, 26 Jul 2021 09:56:02 -0700 Subject: [PATCH 1/5] add azure application credential --- .../identity/AzureApplicationCredential.java | 40 +++++++ .../AzureApplicationCredentialBuilder.java | 106 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java new file mode 100644 index 000000000000..2427848fe6ac --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity; + +import com.azure.core.annotation.Immutable; +import com.azure.core.credential.TokenCredential; + +import java.util.List; + +/** + * Creates a credential using environment variables or the shared token cache. It tries to create a valid credential in + * the following order: + * + *
    + *
  1. {@link EnvironmentCredential}
  2. + *
  3. {@link ManagedIdentityCredential}
  4. + *
  5. {@link SharedTokenCacheCredential}
  6. + *
  7. {@link IntelliJCredential}
  8. + *
  9. {@link VisualStudioCodeCredential}
  10. + *
  11. {@link AzureCliCredential}
  12. + *
  13. Fails if none of the credentials above could be created.
  14. + *
+ */ +@Immutable +public final class AzureApplicationCredential extends ChainedTokenCredential { + /** + * Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID, + * AZURE_CLIENT_SECRET, and AZURE_TENANT_ID environment variables to create a + * ClientSecretCredential. + * + * If these environment variables are not available, then this will use the Shared MSAL + * token cache. + * + * @param tokenCredentials the list of credentials to execute for authentication. + */ + AzureApplicationCredential(List tokenCredentials) { + super(tokenCredentials); + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java new file mode 100644 index 000000000000..6f2d3e4ac56e --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java @@ -0,0 +1,106 @@ +package com.azure.identity; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.util.Configuration; +import com.azure.core.util.logging.ClientLogger; + +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; + +/** + * Fluent credential builder for instantiating a {@link DefaultAzureCredential}. + * + * @see DefaultAzureCredential + */ +public class AzureApplicationCredentialBuilder extends CredentialBuilderBase { + private String tenantId; + private String managedIdentityClientId; + private final ClientLogger logger = new ClientLogger(DefaultAzureCredentialBuilder.class); + + /** + * Creates an instance of a DefaultAzureCredentialBuilder. + */ + public AzureApplicationCredentialBuilder() { + Configuration configuration = Configuration.getGlobalConfiguration().clone(); + tenantId = configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID); + managedIdentityClientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID); + } + + /** + * Sets the tenant id of the user to authenticate through the {@link DefaultAzureCredential}. If unset, the value + * in the AZURE_TENANT_ID environment variable will be used. If neither is set, the default is null + * and will authenticate users to their default tenant. + * + * @param tenantId the tenant ID to set. + * @return An updated instance of this builder with the tenant id set as specified. + */ + public AzureApplicationCredentialBuilder tenantId(String tenantId) { + this.tenantId = tenantId; + return this; + } + + + /** + * Specifies the Azure Active Directory endpoint to acquire tokens. + * @param authorityHost the Azure Active Directory endpoint + * @return An updated instance of this builder with the authority host set as specified. + */ + public AzureApplicationCredentialBuilder authorityHost(String authorityHost) { + this.identityClientOptions.setAuthorityHost(authorityHost); + return this; + } + + + /** + * Specifies the client ID of user assigned or system assigned identity, when this credential is running + * in an environment with managed identities. If unset, the value in the AZURE_CLIENT_ID environment variable + * will be used. If neither is set, the default value is null and will only work with system assigned + * managed identities and not user assigned managed identities. + * + * @param clientId the client ID + * @return the DefaultAzureCredentialBuilder itself + */ + public AzureApplicationCredentialBuilder managedIdentityClientId(String clientId) { + this.managedIdentityClientId = clientId; + return this; + } + + /** + * Specifies the ExecutorService to be used to execute the authentication requests. + * Developer is responsible for maintaining the lifecycle of the ExecutorService. + * + *

+ * If this is not configured, the {@link ForkJoinPool#commonPool()} will be used which is + * also shared with other application tasks. If the common pool is heavily used for other tasks, authentication + * requests might starve and setting up this executor service should be considered. + *

+ * + *

The executor service and can be safely shutdown if the TokenCredential is no longer being used by the + * Azure SDK clients and should be shutdown before the application exits.

+ * + * @param executorService the executor service to use for executing authentication requests. + * @return An updated instance of this builder with the executor service set as specified. + */ + public AzureApplicationCredentialBuilder executorService(ExecutorService executorService) { + this.identityClientOptions.setExecutorService(executorService); + return this; + } + + /** + * Creates new {@link AzureApplicationCredential} with the configured options set. + * + * @return a {@link AzureApplicationCredential} with the current configurations. + */ + public AzureApplicationCredential build() { + return new AzureApplicationCredential(getCredentialsChain()); + } + + private ArrayList getCredentialsChain() { + ArrayList output = new ArrayList(6); + output.add(new EnvironmentCredential(identityClientOptions)); + output.add(new ManagedIdentityCredential(managedIdentityClientId, identityClientOptions)); + return output; + } +} + From 01f65647d8fa750be736686ca5211f87865f9327 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Fri, 30 Jul 2021 12:30:01 -0700 Subject: [PATCH 2/5] update --- .../identity/AzureApplicationCredential.java | 8 +- .../AzureApplicationCredentialBuilder.java | 26 +--- .../identity/ChainedTokenCredential.java | 65 +++++---- .../AzureApplicationCredentialTest.java | 134 ++++++++++++++++++ 4 files changed, 180 insertions(+), 53 deletions(-) create mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java index 2427848fe6ac..ce53d11f58cb 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java @@ -9,16 +9,12 @@ import java.util.List; /** - * Creates a credential using environment variables or the shared token cache. It tries to create a valid credential in - * the following order: + * Creates a credential using environment variables for Azure hosted Environments. It tries to create a valid credential + * in the following order: * *
    *
  1. {@link EnvironmentCredential}
  2. *
  3. {@link ManagedIdentityCredential}
  4. - *
  5. {@link SharedTokenCacheCredential}
  6. - *
  7. {@link IntelliJCredential}
  8. - *
  9. {@link VisualStudioCodeCredential}
  10. - *
  11. {@link AzureCliCredential}
  12. *
  13. Fails if none of the credentials above could be created.
  14. *
*/ diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java index 6f2d3e4ac56e..965d09336668 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java @@ -9,37 +9,21 @@ import java.util.concurrent.ForkJoinPool; /** - * Fluent credential builder for instantiating a {@link DefaultAzureCredential}. + * Fluent credential builder for instantiating a {@link AzureApplicationCredential}. * - * @see DefaultAzureCredential + * @see AzureApplicationCredential */ public class AzureApplicationCredentialBuilder extends CredentialBuilderBase { - private String tenantId; private String managedIdentityClientId; - private final ClientLogger logger = new ClientLogger(DefaultAzureCredentialBuilder.class); /** - * Creates an instance of a DefaultAzureCredentialBuilder. + * Creates an instance of a AzureApplicationCredentialBuilder. */ public AzureApplicationCredentialBuilder() { Configuration configuration = Configuration.getGlobalConfiguration().clone(); - tenantId = configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID); managedIdentityClientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID); } - /** - * Sets the tenant id of the user to authenticate through the {@link DefaultAzureCredential}. If unset, the value - * in the AZURE_TENANT_ID environment variable will be used. If neither is set, the default is null - * and will authenticate users to their default tenant. - * - * @param tenantId the tenant ID to set. - * @return An updated instance of this builder with the tenant id set as specified. - */ - public AzureApplicationCredentialBuilder tenantId(String tenantId) { - this.tenantId = tenantId; - return this; - } - /** * Specifies the Azure Active Directory endpoint to acquire tokens. @@ -59,7 +43,7 @@ public AzureApplicationCredentialBuilder authorityHost(String authorityHost) { * managed identities and not user assigned managed identities. * * @param clientId the client ID - * @return the DefaultAzureCredentialBuilder itself + * @return An updated instance of this builder with the managed identity client id set as specified. */ public AzureApplicationCredentialBuilder managedIdentityClientId(String clientId) { this.managedIdentityClientId = clientId; @@ -97,7 +81,7 @@ public AzureApplicationCredential build() { } private ArrayList getCredentialsChain() { - ArrayList output = new ArrayList(6); + ArrayList output = new ArrayList(2); output.add(new EnvironmentCredential(identityClientOptions)); output.add(new ManagedIdentityCredential(managedIdentityClientId, identityClientOptions)); return output; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java index 8cef48f2a993..ff8700cd0995 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java @@ -27,6 +27,7 @@ public class ChainedTokenCredential implements TokenCredential { private final ClientLogger logger = new ClientLogger(getClass()); private final List credentials; + private volatile TokenCredential successCredential; private final String unavailableError = this.getClass().getSimpleName() + " authentication failed. ---> "; /** @@ -51,32 +52,44 @@ public class ChainedTokenCredential implements TokenCredential { @Override public Mono getToken(TokenRequestContext request) { List exceptions = new ArrayList<>(4); - return Flux.fromIterable(credentials) - .flatMap(p -> p.getToken(request) - .doOnNext(t -> logger.info("Azure Identity => Attempted credential {} returns a token", - p.getClass().getSimpleName())) - .onErrorResume(Exception.class, t -> { - if (!t.getClass().getSimpleName().equals("CredentialUnavailableException")) { - return Mono.error(new ClientAuthenticationException( - unavailableError + p.getClass().getSimpleName() - + " authentication failed. Error Details: " + t.getMessage(), - null, t)); + + if (successCredential != null) { + return successCredential.getToken(request) + .doOnNext(t -> { + logger.info("Azure Identity => Attempted credential {} returns a token", + successCredential.getClass().getSimpleName()); + }); + } else { + return Flux.fromIterable(credentials) + .flatMap(p -> p.getToken(request) + .doOnNext(t -> { + logger.info("Azure Identity => Attempted credential {} returns a token", + p.getClass().getSimpleName()); + successCredential = p; + }) + .onErrorResume(Exception.class, t -> { + if (!t.getClass().getSimpleName().equals("CredentialUnavailableException")) { + return Mono.error(new ClientAuthenticationException( + unavailableError + p.getClass().getSimpleName() + + " authentication failed. Error Details: " + t.getMessage(), + null, t)); + } + exceptions.add((CredentialUnavailableException) t); + logger.info("Azure Identity => Attempted credential {} is unavailable.", + p.getClass().getSimpleName()); + return Mono.empty(); + }), 1) + .next() + .switchIfEmpty(Mono.defer(() -> { + // Chain Exceptions. + CredentialUnavailableException last = exceptions.get(exceptions.size() - 1); + for (int z = exceptions.size() - 2; z >= 0; z--) { + CredentialUnavailableException current = exceptions.get(z); + last = new CredentialUnavailableException(current.getMessage() + "\r\n" + last.getMessage(), + last.getCause()); } - exceptions.add((CredentialUnavailableException) t); - logger.info("Azure Identity => Attempted credential {} is unavailable.", - p.getClass().getSimpleName()); - return Mono.empty(); - }), 1) - .next() - .switchIfEmpty(Mono.defer(() -> { - // Chain Exceptions. - CredentialUnavailableException last = exceptions.get(exceptions.size() - 1); - for (int z = exceptions.size() - 2; z >= 0; z--) { - CredentialUnavailableException current = exceptions.get(z); - last = new CredentialUnavailableException(current.getMessage() + "\r\n" + last.getMessage(), - last.getCause()); - } - return Mono.error(last); - })); + return Mono.error(last); + })); + } } } 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 new file mode 100644 index 000000000000..56c0031507ae --- /dev/null +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity; + +import com.azure.core.credential.TokenRequestContext; +import com.azure.core.util.Configuration; +import com.azure.identity.implementation.IdentityClient; +import com.azure.identity.util.TestUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(fullyQualifiedNames = "com.azure.identity.*") +@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 AzureApplicationCredentialTest { + + private static final String TENANT_ID = "contoso.com"; + private static final String CLIENT_ID = UUID.randomUUID().toString(); + + @Test + public void testUseEnvironmentCredential() throws Exception { + Configuration configuration = Configuration.getGlobalConfiguration(); + + try { + // setup + String secret = "secret"; + String token1 = "token1"; + TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com"); + OffsetDateTime expiresOn = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); + configuration.put("AZURE_CLIENT_ID", CLIENT_ID); + configuration.put("AZURE_CLIENT_SECRET", secret); + configuration.put("AZURE_TENANT_ID", TENANT_ID); + + // mock + 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); + + // test + AzureApplicationCredential credential = new AzureApplicationCredentialBuilder().build(); + StepVerifier.create(credential.getToken(request1)) + .expectNextMatches(accessToken -> token1.equals(accessToken.getToken()) + && expiresOn.getSecond() == accessToken.getExpiresAt().getSecond()) + .verifyComplete(); + } finally { + // clean up + configuration.remove("AZURE_CLIENT_ID"); + configuration.remove("AZURE_CLIENT_SECRET"); + configuration.remove("AZURE_TENANT_ID"); + } + } + + @Test + public void testUseManagedIdentityCredential() throws Exception { + // setup + String token1 = "token1"; + TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); + OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); + + // mock + IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); + when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + + IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class); + when(intelliJCredential.getToken(request)) + .thenReturn(Mono.empty()); + PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments() + .thenReturn(intelliJCredential); + + // test + AzureApplicationCredential credential = new AzureApplicationCredentialBuilder().build(); + StepVerifier.create(credential.getToken(request)) + .expectNextMatches(accessToken -> token1.equals(accessToken.getToken()) + && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) + .verifyComplete(); + } + + @Test + public void testNoCredentialWorks() throws Exception { + // setup + TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); + + // mock + IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); + when(identityClient.authenticateToIMDSEndpoint(request)) + .thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from managed identity"))); + PowerMockito.whenNew(IdentityClient.class).withAnyArguments() + .thenReturn(identityClient); + + // test + AzureApplicationCredential credential = new AzureApplicationCredentialBuilder().build(); + StepVerifier.create(credential.getToken(request)) + .expectErrorMatches(t -> t instanceof CredentialUnavailableException && t.getMessage() + .startsWith("EnvironmentCredential authentication unavailable. ")) + .verify(); + } + + @Test + public void testCredentialUnavailable() throws Exception { + TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); + + ManagedIdentityCredential managedIdentityCredential = PowerMockito.mock(ManagedIdentityCredential.class); + when(managedIdentityCredential.getToken(request)) + .thenReturn(Mono.error( + new CredentialUnavailableException("Cannot get token from Managed Identity credential"))); + PowerMockito.whenNew(ManagedIdentityCredential.class).withAnyArguments() + .thenReturn(managedIdentityCredential); + + // test + AzureApplicationCredential credential = new AzureApplicationCredentialBuilder() + .build(); + StepVerifier.create(credential.getToken(request)) + .expectErrorMatches(t -> t instanceof CredentialUnavailableException && t.getMessage() + .startsWith("EnvironmentCredential authentication unavailable. ")) + .verify(); + } +} From 81604ed85742b2366d7ffc08cafb9d776d032eda Mon Sep 17 00:00:00 2001 From: g2vinay Date: Fri, 6 Aug 2021 11:38:01 -0700 Subject: [PATCH 3/5] update --- .../identity/AzureApplicationCredential.java | 9 ++- .../AzureApplicationCredentialBuilder.java | 3 +- .../identity/ChainedTokenCredential.java | 65 ++++++++----------- 3 files changed, 31 insertions(+), 46 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java index ce53d11f58cb..87b8db33e205 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredential.java @@ -21,12 +21,11 @@ @Immutable public final class AzureApplicationCredential extends ChainedTokenCredential { /** - * Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID, - * AZURE_CLIENT_SECRET, and AZURE_TENANT_ID environment variables to create a - * ClientSecretCredential. + * Creates default AzureApplicationCredential instance to use. This will use environment variables to create + * {@link EnvironmentCredential} * - * If these environment variables are not available, then this will use the Shared MSAL - * token cache. + * If these environment variables are not available, then this will attempt to use Managed Identity Authentication + * via {@link ManagedIdentityCredential}. * * @param tokenCredentials the list of credentials to execute for authentication. */ diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java index 965d09336668..1fec3e6d9704 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java @@ -17,14 +17,13 @@ public class AzureApplicationCredentialBuilder extends CredentialBuilderBase credentials; - private volatile TokenCredential successCredential; private final String unavailableError = this.getClass().getSimpleName() + " authentication failed. ---> "; /** @@ -52,44 +51,32 @@ public class ChainedTokenCredential implements TokenCredential { @Override public Mono getToken(TokenRequestContext request) { List exceptions = new ArrayList<>(4); - - if (successCredential != null) { - return successCredential.getToken(request) - .doOnNext(t -> { - logger.info("Azure Identity => Attempted credential {} returns a token", - successCredential.getClass().getSimpleName()); - }); - } else { - return Flux.fromIterable(credentials) - .flatMap(p -> p.getToken(request) - .doOnNext(t -> { - logger.info("Azure Identity => Attempted credential {} returns a token", - p.getClass().getSimpleName()); - successCredential = p; - }) - .onErrorResume(Exception.class, t -> { - if (!t.getClass().getSimpleName().equals("CredentialUnavailableException")) { - return Mono.error(new ClientAuthenticationException( - unavailableError + p.getClass().getSimpleName() - + " authentication failed. Error Details: " + t.getMessage(), - null, t)); - } - exceptions.add((CredentialUnavailableException) t); - logger.info("Azure Identity => Attempted credential {} is unavailable.", - p.getClass().getSimpleName()); - return Mono.empty(); - }), 1) - .next() - .switchIfEmpty(Mono.defer(() -> { - // Chain Exceptions. - CredentialUnavailableException last = exceptions.get(exceptions.size() - 1); - for (int z = exceptions.size() - 2; z >= 0; z--) { - CredentialUnavailableException current = exceptions.get(z); - last = new CredentialUnavailableException(current.getMessage() + "\r\n" + last.getMessage(), - last.getCause()); + return Flux.fromIterable(credentials) + .flatMap(p -> p.getToken(request) + .doOnNext(t -> logger.info("Azure Identity => Attempted credential {} returns a token", + p.getClass().getSimpleName())) + .onErrorResume(Exception.class, t -> { + if (!t.getClass().getSimpleName().equals("CredentialUnavailableException")) { + return Mono.error(new ClientAuthenticationException( + unavailableError + p.getClass().getSimpleName() + + " authentication failed. Error Details: " + t.getMessage(), + null, t)); } - return Mono.error(last); - })); - } + exceptions.add((CredentialUnavailableException) t); + logger.info("Azure Identity => Attempted credential {} is unavailable.", + p.getClass().getSimpleName()); + return Mono.empty(); + }), 1) + .next() + .switchIfEmpty(Mono.defer(() -> { + // Chain Exceptions. + CredentialUnavailableException last = exceptions.get(exceptions.size() - 1); + for (int z = exceptions.size() - 2; z >= 0; z--) { + CredentialUnavailableException current = exceptions.get(z); + last = new CredentialUnavailableException(current.getMessage() + "\r\n" + last.getMessage(), + last.getCause()); + } + return Mono.error(last); + })); } } From e028600784b2ee4df78196208c95f41c4492d68d Mon Sep 17 00:00:00 2001 From: g2vinay Date: Fri, 6 Aug 2021 12:56:45 -0700 Subject: [PATCH 4/5] fix checkstyle --- .../com/azure/identity/AzureApplicationCredentialBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java index 1fec3e6d9704..e6b250882725 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureApplicationCredentialBuilder.java @@ -1,8 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.azure.identity; import com.azure.core.credential.TokenCredential; import com.azure.core.util.Configuration; -import com.azure.core.util.logging.ClientLogger; import java.util.ArrayList; import java.util.concurrent.ExecutorService; From c4c36651e408f8d0f09f91b536e2c7f2ed9e7863 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Tue, 10 Aug 2021 08:03:57 -0700 Subject: [PATCH 5/5] update --- .../azure/identity/EnvironmentCredential.java | 3 +- .../EnvironmentCredentialBuilder.java | 24 +++++++++- .../implementation/IdentityClientOptions.java | 45 ++++++++++++++++--- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java index 2387ccf1978c..ddedd404519d 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java @@ -47,7 +47,8 @@ public class EnvironmentCredential implements TokenCredential { * @param identityClientOptions the options for configuring the identity client */ EnvironmentCredential(IdentityClientOptions identityClientOptions) { - this.configuration = Configuration.getGlobalConfiguration().clone(); + this.configuration = identityClientOptions.getConfiguration() == null + ? Configuration.getGlobalConfiguration().clone() : identityClientOptions.getConfiguration(); TokenCredential targetCredential = null; String clientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredentialBuilder.java index 2f7fa1184683..ae41633ad224 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredentialBuilder.java @@ -3,6 +3,8 @@ package com.azure.identity; +import com.azure.core.util.Configuration; +import com.azure.core.util.CoreUtils; import com.azure.identity.implementation.util.ValidationUtil; import java.util.concurrent.ExecutorService; @@ -14,6 +16,8 @@ * @see EnvironmentCredential */ public class EnvironmentCredentialBuilder extends CredentialBuilderBase { + private String authorityHost; + /** * Specifies the Azure Active Directory endpoint to acquire tokens. * @param authorityHost the Azure Active Directory endpoint @@ -21,7 +25,7 @@ public class EnvironmentCredentialBuilder extends CredentialBuilderBase Duration.ofSeconds((long) Math.pow(2, i.getSeconds() - 1)); - regionalAuthority = RegionalAuthority.fromString( - configuration.get(Configuration.PROPERTY_AZURE_REGIONAL_AUTHORITY_NAME)); } /** @@ -328,4 +324,41 @@ public IdentityClientOptions setRegionalAuthority(RegionalAuthority regionalAuth public RegionalAuthority getRegionalAuthority() { return regionalAuthority; } + + + /** + * Sets the specified configuration store. + * + * @param configuration the configuration store to be used to read env variables and/or system properties. + * @return the updated identity client options + */ + public IdentityClientOptions setConfiguration(Configuration configuration) { + this.configuration = configuration; + loadFromConfiugration(configuration); + return this; + } + + /** + * Gets the configured configuration store. + * + * @return the configured {@link Configuration} store. + */ + public Configuration getConfiguration() { + return this.configuration; + } + + /** + * Loads the details from the specified Configuration Store. + * + * @return the regional authority value if specified + */ + private IdentityClientOptions loadFromConfiugration(Configuration configuration) { + authorityHost = configuration.get(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, + AzureAuthorityHosts.AZURE_PUBLIC_CLOUD); + ValidationUtil.validateAuthHost(getClass().getSimpleName(), authorityHost); + cp1Disabled = configuration.get(Configuration.PROPERTY_AZURE_IDENTITY_DISABLE_CP1, false); + regionalAuthority = RegionalAuthority.fromString( + configuration.get(Configuration.PROPERTY_AZURE_REGIONAL_AUTHORITY_NAME)); + return this; + } }