diff --git a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml
index 55d45da81bcc..a026297f969d 100644
--- a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml
+++ b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml
@@ -901,6 +901,13 @@
+
+
+
+
+
+
+
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredential.java
index 8a348df83623..ba77c3b4727f 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredential.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredential.java
@@ -12,6 +12,7 @@
import com.azure.identity.implementation.IdentityClient;
import com.azure.identity.implementation.IdentityClientBuilder;
import com.azure.identity.implementation.IdentityClientOptions;
+import com.azure.identity.implementation.IdentitySyncClient;
import com.azure.identity.implementation.util.ValidationUtil;
import reactor.core.publisher.Mono;
@@ -54,6 +55,7 @@
public class WorkloadIdentityCredential implements TokenCredential {
private static final ClientLogger LOGGER = new ClientLogger(WorkloadIdentityCredential.class);
private final IdentityClient identityClient;
+ private final IdentitySyncClient identitySyncClient;
/**
* WorkloadIdentityCredential supports Azure workload identity on Kubernetes.
@@ -82,14 +84,16 @@ public class WorkloadIdentityCredential implements TokenCredential {
|| CoreUtils.isNullOrEmpty(federatedTokenFilePathInput)
|| CoreUtils.isNullOrEmpty(clientIdInput)
|| CoreUtils.isNullOrEmpty(identityClientOptions.getAuthorityHost()))) {
- identityClient = new IdentityClientBuilder()
+ IdentityClientBuilder builder = new IdentityClientBuilder()
.clientAssertionPath(federatedTokenFilePathInput)
.clientId(clientId)
.tenantId(tenantIdInput)
- .identityClientOptions(identityClientOptions)
- .build();
+ .identityClientOptions(identityClientOptions);
+ identityClient = builder.build();
+ identitySyncClient = builder.buildSyncClient();
} else {
identityClient = null;
+ identitySyncClient = null;
}
}
@@ -101,7 +105,18 @@ public Mono getToken(TokenRequestContext request) {
+ " guide for more information."
+ " https://aka.ms/azsdk/java/identity/workloadidentitycredential/troubleshoot")));
}
- return identityClient.authenticateWithExchangeToken(request);
+ return identityClient.authenticateWithWorkloadIdentityConfidentialClient(request);
+ }
+
+ @Override
+ public AccessToken getTokenSync(TokenRequestContext request) {
+ if (identitySyncClient == null) {
+ throw LOGGER.logExceptionAsError(new CredentialUnavailableException("WorkloadIdentityCredential"
+ + " authentication unavailable. The workload options are not fully configured. See the troubleshooting"
+ + " guide for more information."
+ + " https://aka.ms/azsdk/java/identity/workloadidentitycredential/troubleshoot"));
+ }
+ return identitySyncClient.authenticateWithWorkloadIdentityConfidentialClient(request);
}
String getClientId() {
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 8ce2ff9e8f07..b9eb8931d4dc 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
@@ -18,6 +18,7 @@
import com.azure.identity.implementation.util.ScopeUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.microsoft.aad.msal4j.AuthorizationCodeParameters;
+import com.microsoft.aad.msal4j.AppTokenProviderParameters;
import com.microsoft.aad.msal4j.ClaimsRequest;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
@@ -30,13 +31,13 @@
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.RefreshTokenParameters;
import com.microsoft.aad.msal4j.SilentParameters;
+import com.microsoft.aad.msal4j.TokenProviderResult;
import com.microsoft.aad.msal4j.UserNamePasswordParameters;
import com.sun.jna.Platform;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.net.ssl.HttpsURLConnection;
-import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
@@ -62,6 +63,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -72,6 +74,7 @@ public class IdentityClient extends IdentityClientBase {
private final SynchronizedAccessor publicClientApplicationAccessor;
private final SynchronizedAccessor confidentialClientApplicationAccessor;
private final SynchronizedAccessor managedIdentityConfidentialClientApplicationAccessor;
+ private final SynchronizedAccessor workloadIdentityConfidentialClientApplicationAccessor;
private final SynchronizedAccessor clientAssertionAccessor;
@@ -106,6 +109,9 @@ public class IdentityClient extends IdentityClientBase {
this.managedIdentityConfidentialClientApplicationAccessor =
new SynchronizedAccessor<>(this::getManagedIdentityConfidentialClientApplication);
+ this.workloadIdentityConfidentialClientApplicationAccessor =
+ new SynchronizedAccessor<>(this::getWorkloadIdentityConfidentialClientApplication);
+
Duration cacheTimeout = (clientAssertionTimeout == null) ? Duration.ofMinutes(5) : clientAssertionTimeout;
this.clientAssertionAccessor = new SynchronizedAccessor<>(this::parseClientAssertion, cacheTimeout);
}
@@ -130,6 +136,16 @@ private Mono getManagedIdentityConfidentialClient
});
}
+ private Mono getWorkloadIdentityConfidentialClientApplication() {
+ return Mono.defer(() -> {
+ try {
+ return Mono.just(super.getWorkloadIdentityConfidentialClient());
+ } catch (RuntimeException e) {
+ return Mono.error(e);
+ }
+ });
+ }
+
@Override
Mono getTokenFromTargetManagedIdentity(TokenRequestContext tokenRequestContext) {
ManagedIdentityParameters parameters = options.getManagedIdentityParameters();
@@ -492,10 +508,6 @@ public Mono authenticateWithConfidentialClient(TokenRequestContext
builder.clientCredential(ClientCredentialFactory
.createFromClientAssertion(clientAssertionSupplier.get()));
}
- if (request.getClaims() != null) {
- ClaimsRequest customClaimRequest = CustomClaimRequest.formatAsClaimsRequest(request.getClaims());
- builder.claims(customClaimRequest);
- }
return confidentialClient.acquireToken(builder.build());
}
)).map(MsalToken::new);
@@ -514,6 +526,19 @@ public Mono authenticateWithManagedIdentityConfidentialClient(Token
.map(MsalToken::new);
}
+ public Mono authenticateWithWorkloadIdentityConfidentialClient(TokenRequestContext request) {
+ return workloadIdentityConfidentialClientApplicationAccessor.getValue()
+ .flatMap(confidentialClient -> Mono.fromFuture(() -> {
+ ClientCredentialParameters.ClientCredentialParametersBuilder builder =
+ ClientCredentialParameters.builder(new HashSet<>(request.getScopes()))
+ .tenant(IdentityUtil
+ .resolveTenantId(tenantId, request, options));
+ return confidentialClient.acquireToken(builder.build());
+ }
+ )).onErrorMap(t -> new CredentialUnavailableException("Managed Identity authentication is not available.", t))
+ .map(MsalToken::new);
+ }
+
/**
* Asynchronously acquire a token from Active Directory with a username and a password.
*
@@ -602,11 +627,6 @@ public Mono authenticateWithConfidentialClientCache(TokenRequestCon
SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder(
new HashSet<>(request.getScopes()))
.tenant(IdentityUtil.resolveTenantId(tenantId, request, options));
- if (request.getClaims() != null) {
- ClaimsRequest customClaimRequest = CustomClaimRequest
- .formatAsClaimsRequest(request.getClaims());
- parametersBuilder.claims(customClaimRequest);
- }
try {
return confidentialClient.acquireTokenSilently(parametersBuilder.build());
} catch (MalformedURLException e) {
@@ -902,41 +922,7 @@ private Mono authenticateToArcManagedIdentityEndpoint(String identi
public Mono authenticateWithExchangeToken(TokenRequestContext request) {
return clientAssertionAccessor.getValue()
- .flatMap(assertionToken -> Mono.fromCallable(() -> {
- String authorityUrl = TRAILING_FORWARD_SLASHES.matcher(options.getAuthorityHost()).replaceAll("")
- + "/" + tenantId + "/oauth2/v2.0/token";
-
- String urlParams = "client_assertion=" + assertionToken
- + "&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_id="
- + clientId + "&grant_type=client_credentials&scope=" + urlEncode(request.getScopes().get(0));
-
- byte[] postData = urlParams.getBytes(StandardCharsets.UTF_8);
- int postDataLength = postData.length;
-
- HttpURLConnection connection = null;
-
- URL url = getUrl(authorityUrl);
-
- try {
- connection = (HttpURLConnection) url.openConnection();
- connection.setRequestMethod("POST");
- connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
- connection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
- connection.setRequestProperty("User-Agent", userAgent);
- connection.setDoOutput(true);
- try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
- outputStream.write(postData);
- }
- connection.connect();
-
- return SERIALIZER_ADAPTER.deserialize(connection.getInputStream(), MSIToken.class,
- SerializerEncoding.JSON);
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }));
+ .flatMap(assertionToken -> Mono.fromCallable(() -> authenticateWithExchangeTokenHelper(request, assertionToken)));
}
/**
@@ -1088,9 +1074,6 @@ public Mono authenticateToManagedIdentityEndpoint(String identityEn
});
}
- static URL getUrl(String uri) throws MalformedURLException {
- return new URL(uri);
- }
/**
* Asynchronously acquire a token from the Virtual Machine IMDS endpoint.
*
@@ -1237,8 +1220,6 @@ private static Proxy proxyOptionsToJavaNetProxy(ProxyOptions options) {
}
}
-
-
void openUrl(String url) throws IOException {
Runtime rt = Runtime.getRuntime();
@@ -1273,7 +1254,22 @@ private boolean isADFSTenant() {
return ADFS_TENANT.equals(this.tenantId);
}
- private static String urlEncode(String value) throws IOException {
- return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
+ Function> getWorkloadIdentityTokenProvider() {
+ return appTokenProviderParameters -> {
+ TokenRequestContext trc = new TokenRequestContext()
+ .setScopes(new ArrayList<>(appTokenProviderParameters.scopes))
+ .setClaims(appTokenProviderParameters.claims)
+ .setTenantId(appTokenProviderParameters.tenantId);
+
+ Mono accessTokenAsync = authenticateWithExchangeToken(trc);
+
+ return accessTokenAsync.map(accessToken -> {
+ TokenProviderResult result = new TokenProviderResult();
+ result.setAccessToken(accessToken.getToken());
+ result.setTenantId(trc.getTenantId());
+ result.setExpiresInSeconds(accessToken.getExpiresAt().toEpochSecond());
+ return result;
+ }).toFuture();
+ };
}
}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBase.java
index 6214672d1c58..e3dc53d159e3 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBase.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBase.java
@@ -32,6 +32,7 @@
import com.azure.identity.implementation.util.CertificateUtil;
import com.azure.identity.implementation.util.IdentityUtil;
import com.azure.identity.implementation.util.LoggingUtil;
+import com.microsoft.aad.msal4j.AppTokenProviderParameters;
import com.microsoft.aad.msal4j.ClaimsRequest;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
@@ -48,14 +49,18 @@
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -74,8 +79,10 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@@ -224,12 +231,6 @@ ConfidentialClientApplication getConfidentialClient() {
applicationBuilder.executorService(options.getExecutorService());
}
- if (!options.isCp1Disabled()) {
- Set set = new HashSet<>(1);
- set.add("CP1");
- applicationBuilder.clientCapabilities(set);
- }
-
TokenCachePersistenceOptions tokenCachePersistenceOptions = options.getTokenCacheOptions();
PersistentTokenCacheImpl tokenCache = null;
if (tokenCachePersistenceOptions != null) {
@@ -363,6 +364,42 @@ ConfidentialClientApplication getManagedIdentityConfidentialClient() {
return applicationBuilder.build();
}
+ ConfidentialClientApplication getWorkloadIdentityConfidentialClient() {
+ String authorityUrl = TRAILING_FORWARD_SLASHES.matcher(options.getAuthorityHost()).replaceAll("")
+ + "/" + tenantId;
+
+ // Temporarily pass in Dummy Client secret and Client ID. until MSal removes its requirements.
+ IClientCredential credential = ClientCredentialFactory
+ .createFromSecret(clientSecret != null ? clientSecret : "dummy-secret");
+ ConfidentialClientApplication.Builder applicationBuilder =
+ ConfidentialClientApplication.builder(clientId == null ? "SYSTEM-ASSIGNED-MANAGED-IDENTITY"
+ : clientId, credential);
+
+ try {
+ applicationBuilder = applicationBuilder.authority(authorityUrl).instanceDiscovery(options.getDisableAuthorityValidationAndInstanceDiscovery());
+ } catch (MalformedURLException e) {
+ throw LOGGER.logExceptionAsWarning(new IllegalStateException(e));
+ }
+
+ applicationBuilder.appTokenProvider(getWorkloadIdentityTokenProvider());
+
+
+ initializeHttpPipelineAdapter();
+ if (httpPipelineAdapter != null) {
+ applicationBuilder.httpClient(httpPipelineAdapter);
+ } else {
+ applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions()));
+ }
+
+ if (options.getExecutorService() != null) {
+ applicationBuilder.executorService(options.getExecutorService());
+ }
+
+ return applicationBuilder.build();
+ }
+
+ abstract Function> getWorkloadIdentityTokenProvider();
+
DeviceCodeFlowParameters.DeviceCodeFlowParametersBuilder buildDeviceCodeFlowParameters(TokenRequestContext request, Consumer deviceCodeConsumer) {
DeviceCodeFlowParameters.DeviceCodeFlowParametersBuilder parametersBuilder =
DeviceCodeFlowParameters.builder(
@@ -383,11 +420,6 @@ OnBehalfOfParameters buildOBOFlowParameters(TokenRequestContext request) {
OnBehalfOfParameters.OnBehalfOfParametersBuilder builder = OnBehalfOfParameters
.builder(new HashSet<>(request.getScopes()), options.getUserAssertion())
.tenant(IdentityUtil.resolveTenantId(tenantId, request, options));
-
- if (request.getClaims() != null) {
- ClaimsRequest customClaimRequest = CustomClaimRequest.formatAsClaimsRequest(request.getClaims());
- builder.claims(customClaimRequest);
- }
return builder.build();
}
@@ -613,6 +645,42 @@ AccessToken getTokenFromAzureDeveloperCLIAuthentication(StringBuilder azdCommand
return token;
}
+ AccessToken authenticateWithExchangeTokenHelper(TokenRequestContext request, String assertionToken) throws IOException {
+ String authorityUrl = TRAILING_FORWARD_SLASHES.matcher(options.getAuthorityHost()).replaceAll("")
+ + "/" + tenantId + "/oauth2/v2.0/token";
+
+ String urlParams = "client_assertion=" + assertionToken
+ + "&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_id="
+ + clientId + "&grant_type=client_credentials&scope=" + urlEncode(request.getScopes().get(0));
+
+ byte[] postData = urlParams.getBytes(StandardCharsets.UTF_8);
+ int postDataLength = postData.length;
+
+ HttpURLConnection connection = null;
+
+ URL url = getUrl(authorityUrl);
+
+ try {
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+ connection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
+ connection.setRequestProperty("User-Agent", userAgent);
+ connection.setDoOutput(true);
+ try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
+ outputStream.write(postData);
+ }
+ connection.connect();
+
+ return SERIALIZER_ADAPTER.deserialize(connection.getInputStream(), MSIToken.class,
+ SerializerEncoding.JSON);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
String getSafeWorkingDirectory() {
if (isWindowsPlatform()) {
String windowsSystemRoot = System.getenv("SystemRoot");
@@ -724,6 +792,14 @@ private static Proxy proxyOptionsToJavaNetProxy(ProxyOptions options) {
}
}
+ static String urlEncode(String value) throws IOException {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
+ }
+
+ static URL getUrl(String uri) throws MalformedURLException {
+ return new URL(uri);
+ }
+
/**
* Get the configured tenant id.
*
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentitySyncClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentitySyncClient.java
index 3422f997c83c..6c2eb9415326 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentitySyncClient.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentitySyncClient.java
@@ -12,6 +12,7 @@
import com.azure.identity.implementation.util.IdentityUtil;
import com.azure.identity.implementation.util.LoggingUtil;
import com.azure.identity.implementation.util.ScopeUtil;
+import com.microsoft.aad.msal4j.AppTokenProviderParameters;
import com.microsoft.aad.msal4j.ClaimsRequest;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
@@ -22,11 +23,13 @@
import com.microsoft.aad.msal4j.InteractiveRequestParameters;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.SilentParameters;
+import com.microsoft.aad.msal4j.TokenProviderResult;
import com.microsoft.aad.msal4j.UserNamePasswordParameters;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -35,10 +38,13 @@
import java.nio.file.Paths;
import java.time.Duration;
import java.time.OffsetDateTime;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Supplier;
public class IdentitySyncClient extends IdentityClientBase {
@@ -46,6 +52,7 @@ public class IdentitySyncClient extends IdentityClientBase {
private final SynchronousAccessor publicClientApplicationAccessor;
private final SynchronousAccessor confidentialClientApplicationAccessor;
private final SynchronousAccessor managedIdentityConfidentialClientApplicationAccessor;
+ private final SynchronousAccessor workloadIdentityConfidentialClientApplicationAccessor;
private final SynchronousAccessor clientAssertionAccessor;
@@ -80,6 +87,9 @@ public class IdentitySyncClient extends IdentityClientBase {
this.managedIdentityConfidentialClientApplicationAccessor = new SynchronousAccessor<>(() ->
this.getManagedIdentityConfidentialClient());
+ this.workloadIdentityConfidentialClientApplicationAccessor = new SynchronousAccessor<>(() ->
+ this.getWorkloadIdentityConfidentialClient());
+
this.clientAssertionAccessor = clientAssertionTimeout == null
? new SynchronousAccessor<>(() -> parseClientAssertion(), Duration.ofMinutes(5))
: new SynchronousAccessor<>(() -> parseClientAssertion(), clientAssertionTimeout);
@@ -373,7 +383,7 @@ public AccessToken authenticateWithAzureDeveloperCli(TokenRequestContext request
? LoggingUtil.logCredentialUnavailableException(LOGGER, options, (CredentialUnavailableException) e)
: LOGGER.logExceptionAsError(e));
}
- }
+ }
/**
* Asynchronously acquire a token from Active Directory with Azure PowerShell.
@@ -390,6 +400,54 @@ public AccessToken authenticateWithOBO(TokenRequestContext request) {
}
}
+ public AccessToken authenticateWithExchangeTokenSync(TokenRequestContext request) {
+
+ try {
+ String assertionToken = clientAssertionAccessor.getValue();
+ return authenticateWithExchangeTokenHelper(request, assertionToken);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ Function> getWorkloadIdentityTokenProvider() {
+ return appTokenProviderParameters -> {
+ TokenRequestContext trc = new TokenRequestContext()
+ .setScopes(new ArrayList<>(appTokenProviderParameters.scopes))
+ .setClaims(appTokenProviderParameters.claims)
+ .setTenantId(appTokenProviderParameters.tenantId);
+
+ AccessToken accessToken = authenticateWithExchangeTokenSync(trc);
+
+ Supplier tokenProviderResultSupplier = () -> {
+ TokenProviderResult result = new TokenProviderResult();
+ result.setAccessToken(accessToken.getToken());
+ result.setTenantId(trc.getTenantId());
+ result.setExpiresInSeconds(accessToken.getExpiresAt().toEpochSecond());
+ return result;
+ };
+
+ return options.getExecutorService() != null
+ ? CompletableFuture.supplyAsync(tokenProviderResultSupplier, options.getExecutorService())
+ : CompletableFuture.supplyAsync(tokenProviderResultSupplier);
+ };
+ }
+
+ public AccessToken authenticateWithWorkloadIdentityConfidentialClient(TokenRequestContext request) {
+ ConfidentialClientApplication confidentialClient =
+ workloadIdentityConfidentialClientApplicationAccessor.getValue();
+
+ try {
+ ClientCredentialParameters.ClientCredentialParametersBuilder builder =
+ ClientCredentialParameters.builder(new HashSet<>(request.getScopes()))
+ .tenant(IdentityUtil
+ .resolveTenantId(tenantId, request, options));
+ return new MsalToken(confidentialClient.acquireToken(builder.build()).get());
+ } catch (Exception e) {
+ throw new CredentialUnavailableException("Managed Identity authentication is not available.", e);
+ }
+ }
+
/**
* Get the configured identity client options.
*
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 056ba65c473f..739f46e717b2 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
@@ -100,7 +100,7 @@ public void testUseWorkloadIdentityCredentialWithManagedIdentityClientId() {
// mock
try (MockedConstruction mocked = mockConstruction(IdentityClient.class, (identityClient, context) -> {
when(identityClient.authenticateWithAzureDeveloperCli(request)).thenReturn(Mono.empty());
- when(identityClient.authenticateWithExchangeToken(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
+ when(identityClient.authenticateWithWorkloadIdentityConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
}); MockedConstruction ijcredential = mockConstruction(IntelliJCredential.class, (intelliJCredential, context) -> {
when(intelliJCredential.getToken(request)).thenReturn(Mono.empty());
})) {
@@ -130,7 +130,7 @@ public void testUseWorkloadIdentityCredentialWithWorkloadClientId() {
// mock
try (MockedConstruction mocked = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithExchangeToken(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
+ when(identityClient.authenticateWithWorkloadIdentityConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
})) {
// test
String clientId = "dummy-client-id";
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java
index 36e1553de0fc..de9b862b3329 100644
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java
+++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java
@@ -3,10 +3,12 @@
package com.azure.identity;
+import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.test.utils.TestConfigurationSource;
import com.azure.core.util.Configuration;
import com.azure.identity.implementation.IdentityClient;
+import com.azure.identity.implementation.IdentitySyncClient;
import com.azure.identity.util.TestUtils;
import org.junit.Assert;
import org.junit.Test;
@@ -36,7 +38,7 @@ public void testWorkloadIdentityFlow() {
// mock
try (MockedConstruction identityClientMock = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithExchangeToken(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
+ when(identityClient.authenticateWithWorkloadIdentityConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
})) {
// test
WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder()
@@ -52,6 +54,35 @@ public void testWorkloadIdentityFlow() {
}
}
+ @Test
+ public void testWorkloadIdentityFlowSync() {
+ // setup
+ String endpoint = "https://localhost";
+ String token1 = "token1";
+ TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com");
+ OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource()
+ .put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint));
+
+ // mock
+ try (MockedConstruction identityClientMock = mockConstruction(IdentitySyncClient.class, (identityClient, context) -> {
+ when(identityClient.authenticateWithWorkloadIdentityConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessTokenSync(token1, expiresAt));
+ })) {
+ // test
+ WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder()
+ .tenantId("dummy-tenantid")
+ .clientId("dummy-clientid")
+ .tokenFilePath("dummy-path")
+ .configuration(configuration).clientId(CLIENT_ID).build();
+
+ AccessToken token = credential.getTokenSync(request1);
+
+ Assert.assertTrue(token1.equals(token.getToken()));
+ Assert.assertTrue(expiresAt.getSecond() == token.getExpiresAt().getSecond());
+ Assert.assertNotNull(identityClientMock);
+ }
+ }
+
@Test(expected = IllegalArgumentException.class)
public void testWorkloadIdentityFlowFailureNoTenantId() {
// setup
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java
index aaa671b82bef..ed2baf965e50 100644
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java
+++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java
@@ -640,7 +640,7 @@ private void mockForClientPemCertificate(String accessToken, TokenRequestContext
}
private void mockForMSICodeFlow(String tokenJson, Runnable test) throws Exception {
- try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClient.class)) {
+ try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClientBase.class)) {
URL url = mock(URL.class);
HttpURLConnection huc = mock(HttpURLConnection.class);
doNothing().when(huc).setRequestMethod(anyString());
@@ -649,14 +649,14 @@ private void mockForMSICodeFlow(String tokenJson, Runnable test) throws Exceptio
when(url.openConnection()).thenReturn(huc);
InputStream inputStream = new ByteArrayInputStream(tokenJson.getBytes(Charset.defaultCharset()));
when(huc.getInputStream()).thenReturn(inputStream);
- identityClientMockedStatic.when(() -> IdentityClient.getUrl(anyString())).thenReturn(url);
+ identityClientMockedStatic.when(() -> IdentityClientBase.getUrl(anyString())).thenReturn(url);
test.run();
}
}
private void mockForServiceFabricCodeFlow(String tokenJson, Runnable test) throws Exception {
- try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClient.class)) {
+ try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClientBase.class)) {
URL url = mock(URL.class);
HttpsURLConnection huc = mock(HttpsURLConnection.class);
doNothing().when(huc).setRequestMethod(anyString());
@@ -666,13 +666,13 @@ private void mockForServiceFabricCodeFlow(String tokenJson, Runnable test) throw
when(url.openConnection()).thenReturn(huc);
InputStream inputStream = new ByteArrayInputStream(tokenJson.getBytes(Charset.defaultCharset()));
when(huc.getInputStream()).thenReturn(inputStream);
- identityClientMockedStatic.when(() -> IdentityClient.getUrl(anyString())).thenReturn(url);
+ identityClientMockedStatic.when(() -> IdentityClientBase.getUrl(anyString())).thenReturn(url);
test.run();
}
}
private void mockForArcCodeFlow(int responseCode, Runnable test) throws Exception {
- try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClient.class)) {
+ try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClientBase.class)) {
URL url = mock(URL.class);
HttpURLConnection huc = mock(HttpURLConnection.class);
doNothing().when(huc).setRequestMethod(anyString());
@@ -681,13 +681,13 @@ private void mockForArcCodeFlow(int responseCode, Runnable test) throws Exceptio
when(url.openConnection()).thenReturn(huc);
when(huc.getInputStream()).thenThrow(new IOException());
when(huc.getResponseCode()).thenReturn(responseCode);
- identityClientMockedStatic.when(() -> IdentityClient.getUrl(anyString())).thenReturn(url);
+ identityClientMockedStatic.when(() -> IdentityClientBase.getUrl(anyString())).thenReturn(url);
test.run();
}
}
private void mockForIMDSCodeFlow(String endpoint, String tokenJson, Runnable test) throws Exception {
- try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClient.class)) {
+ try (MockedStatic identityClientMockedStatic = mockStatic(IdentityClientBase.class)) {
URL url = mock(URL.class);
HttpURLConnection huc = mock(HttpURLConnection.class);
doNothing().when(huc).setRequestMethod(anyString());
@@ -696,7 +696,7 @@ private void mockForIMDSCodeFlow(String endpoint, String tokenJson, Runnable tes
when(url.openConnection()).thenReturn(huc);
InputStream inputStream = new ByteArrayInputStream(tokenJson.getBytes(Charset.defaultCharset()));
when(huc.getInputStream()).thenReturn(inputStream);
- identityClientMockedStatic.when(() -> IdentityClient.getUrl(anyString())).thenReturn(url);
+ identityClientMockedStatic.when(() -> IdentityClientBase.getUrl(anyString())).thenReturn(url);
test.run();
}
}