com.azure.identity.implementation.IdentityClientBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of azure-identity Show documentation
Show all versions of azure-identity Show documentation
This module contains client library for Microsoft Azure Identity.
The newest version!
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.identity.implementation;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.ProofOfPossessionOptions;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.exception.ClientAuthenticationException;
import com.azure.core.http.HttpHeader;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.HttpPipelineBuilder;
import com.azure.core.http.ProxyOptions;
import com.azure.core.http.policy.AddHeadersPolicy;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.http.policy.HttpLoggingPolicy;
import com.azure.core.http.policy.HttpPipelinePolicy;
import com.azure.core.http.policy.HttpPolicyProviders;
import com.azure.core.http.policy.RetryPolicy;
import com.azure.core.http.policy.UserAgentPolicy;
import com.azure.core.util.ClientOptions;
import com.azure.core.util.Configuration;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.UserAgentUtil;
import com.azure.core.util.builder.ClientBuilderUtil;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.logging.LogLevel;
import com.azure.identity.BrowserCustomizationOptions;
import com.azure.identity.CredentialUnavailableException;
import com.azure.identity.DeviceCodeInfo;
import com.azure.identity.TokenCachePersistenceOptions;
import com.azure.identity.implementation.models.AzureCliToken;
import com.azure.identity.implementation.util.CertificateUtil;
import com.azure.identity.implementation.util.IdentityUtil;
import com.azure.identity.implementation.util.LoggingUtil;
import com.azure.json.JsonProviders;
import com.azure.json.JsonReader;
import com.microsoft.aad.msal4j.AppTokenProviderParameters;
import com.microsoft.aad.msal4j.ClaimsRequest;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.DeviceCodeFlowParameters;
import com.microsoft.aad.msal4j.HttpMethod;
import com.microsoft.aad.msal4j.IBroker;
import com.microsoft.aad.msal4j.IClientCredential;
import com.microsoft.aad.msal4j.InteractiveRequestParameters;
import com.microsoft.aad.msal4j.ManagedIdentityApplication;
import com.microsoft.aad.msal4j.ManagedIdentityId;
import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
import com.microsoft.aad.msal4j.OnBehalfOfParameters;
import com.microsoft.aad.msal4j.Prompt;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.SystemBrowserOptions;
import com.microsoft.aad.msal4j.TokenProviderResult;
import com.microsoft.aad.msal4j.UserNamePasswordParameters;
import reactor.core.publisher.Mono;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
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;
import static com.azure.identity.implementation.util.IdentityUtil.isWindowsPlatform;
public abstract class IdentityClientBase {
static final String WINDOWS_STARTER = "cmd.exe";
static final String LINUX_MAC_STARTER = "/bin/sh";
static final String WINDOWS_SWITCHER = "/c";
static final String LINUX_MAC_SWITCHER = "-c";
static final Pattern WINDOWS_PROCESS_ERROR_MESSAGE = Pattern.compile("'azd?' is not recognized");
static final Pattern SH_PROCESS_ERROR_MESSAGE = Pattern.compile("azd?:.*not found");
static final String DEFAULT_MAC_LINUX_PATH = "/bin/";
static final Duration REFRESH_OFFSET = Duration.ofMinutes(5);
static final String IDENTITY_ENDPOINT_VERSION = "2019-08-01";
static final String MSI_ENDPOINT_VERSION = "2017-09-01";
static final String ARC_MANAGED_IDENTITY_ENDPOINT_API_VERSION = "2019-11-01";
static final String ADFS_TENANT = "adfs";
static final String HTTP_LOCALHOST = "http://localhost";
static final String SERVICE_FABRIC_MANAGED_IDENTITY_API_VERSION = "2019-07-01-preview";
static final ClientLogger LOGGER = new ClientLogger(IdentityClient.class);
static final Pattern ACCESS_TOKEN_PATTERN = Pattern.compile("\"accessToken\": \"(.*?)(\"|$)");
static final Pattern TRAILING_FORWARD_SLASHES = Pattern.compile("/+$");
private static final String AZURE_IDENTITY_PROPERTIES = "azure-identity.properties";
private static final String SDK_NAME = "name";
private static final String SDK_VERSION = "version";
private static final ClientOptions DEFAULT_CLIENT_OPTIONS = new ClientOptions();
private static final Map HTTP_METHOD_HASH_MAP = new HashMap<>(8);
private final Map properties = CoreUtils.getProperties(AZURE_IDENTITY_PROPERTIES);
final IdentityClientOptions options;
final String tenantId;
final String clientId;
final String resourceId;
final String objectId;
final String clientSecret;
final String clientAssertionFilePath;
final byte[] certificate;
final String certificatePath;
final Supplier clientAssertionSupplier;
final Function clientAssertionSupplierWithHttpPipeline;
final String certificatePassword;
HttpPipelineAdapter httpPipelineAdapter;
String userAgent = UserAgentUtil.DEFAULT_USER_AGENT_HEADER;
private Class> interactiveBrowserBroker;
private Method getMsalRuntimeBroker;
HttpPipeline httpPipeline;
/**
* Creates an IdentityClient with the given options.
*
* @param tenantId the tenant ID of the application.
* @param clientId the client ID of the application.
* @param clientSecret the client secret of the application.
* @param resourceId the resource ID of the application
* @param certificatePath the path to the PKCS12 or PEM certificate of the application.
* @param certificate the PKCS12 or PEM certificate of the application.
* @param certificatePassword the password protecting the PFX certificate.
* @param isSharedTokenCacheCredential Indicate whether the credential is
* {@link com.azure.identity.SharedTokenCacheCredential} or not.
* @param clientAssertionTimeout the timeout to use for the client assertion.
* @param options the options configuring the client.
*/
IdentityClientBase(String tenantId, String clientId, String clientSecret, String certificatePath,
String clientAssertionFilePath, String resourceId, String objectId, Supplier clientAssertionSupplier,
Function clientAssertionSupplierWithHttpPipeline, byte[] certificate,
String certificatePassword, boolean isSharedTokenCacheCredential, Duration clientAssertionTimeout,
IdentityClientOptions options) {
if (tenantId == null) {
tenantId = IdentityUtil.DEFAULT_TENANT;
options.setAdditionallyAllowedTenants(Collections.singletonList(IdentityUtil.ALL_TENANTS));
}
if (options == null) {
options = new IdentityClientOptions();
}
this.tenantId = tenantId;
this.clientId = clientId;
this.objectId = objectId;
this.resourceId = resourceId;
this.clientSecret = clientSecret;
this.clientAssertionFilePath = clientAssertionFilePath;
this.certificatePath = certificatePath;
this.certificate = certificate;
this.certificatePassword = certificatePassword;
this.clientAssertionSupplier = clientAssertionSupplier;
this.clientAssertionSupplierWithHttpPipeline = clientAssertionSupplierWithHttpPipeline;
this.options = options;
}
ConfidentialClientApplication getConfidentialClient(boolean enableCae) {
if (clientId == null) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException(
"A non-null value for client ID must be provided for user authentication."));
}
String authorityUrl
= TRAILING_FORWARD_SLASHES.matcher(options.getAuthorityHost()).replaceAll("") + "/" + tenantId;
IClientCredential credential;
if (clientSecret != null) {
credential = ClientCredentialFactory.createFromSecret(clientSecret);
} else if (certificate != null || certificatePath != null) {
try {
byte[] certificateBytes = getCertificateBytes();
if (CertificateUtil.isPem(certificateBytes)) {
List x509CertificateList = CertificateUtil.publicKeyFromPem(certificateBytes);
PrivateKey privateKey = CertificateUtil.privateKeyFromPem(certificateBytes);
if (x509CertificateList.size() == 1) {
credential
= ClientCredentialFactory.createFromCertificate(privateKey, x509CertificateList.get(0));
} else {
credential
= ClientCredentialFactory.createFromCertificateChain(privateKey, x509CertificateList);
}
} else {
try (InputStream pfxCertificateStream = getCertificateInputStream()) {
credential
= ClientCredentialFactory.createFromCertificate(pfxCertificateStream, certificatePassword);
}
}
} catch (IOException | GeneralSecurityException e) {
throw LOGGER.logExceptionAsError(
new RuntimeException("Failed to parse the certificate for the credential: " + e.getMessage(), e));
}
} else if (clientAssertionSupplier != null) {
credential = ClientCredentialFactory.createFromClientAssertion(clientAssertionSupplier.get());
} else if (clientAssertionSupplierWithHttpPipeline != null) {
credential = ClientCredentialFactory
.createFromClientAssertion(clientAssertionSupplierWithHttpPipeline.apply(getPipeline()));
} else {
throw LOGGER.logExceptionAsError(
new IllegalArgumentException("Must provide client secret or client certificate path."
+ " To mitigate this issue, please refer to the troubleshooting guidelines here at "
+ "https://aka.ms/azsdk/java/identity/serviceprincipalauthentication/troubleshoot"));
}
ConfidentialClientApplication.Builder applicationBuilder
= ConfidentialClientApplication.builder(clientId, credential);
try {
applicationBuilder = applicationBuilder.logPii(options.isUnsafeSupportLoggingEnabled())
.authority(authorityUrl)
.instanceDiscovery(options.isInstanceDiscoveryEnabled());
if (!options.isInstanceDiscoveryEnabled()) {
LOGGER.log(LogLevel.VERBOSE, () -> "Instance discovery and authority validation is disabled. In this"
+ " state, the library will not fetch metadata to validate the specified authority host. As a"
+ " result, it is crucial to ensure that the configured authority host is valid and trustworthy.");
}
} catch (MalformedURLException e) {
throw LOGGER.logExceptionAsWarning(new IllegalStateException(e));
}
if (enableCae) {
Set set = new HashSet<>(1);
set.add("CP1");
applicationBuilder.clientCapabilities(set);
}
applicationBuilder.sendX5c(options.isIncludeX5c());
initializeHttpPipelineAdapter();
if (httpPipelineAdapter != null) {
applicationBuilder.httpClient(httpPipelineAdapter);
} else {
applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions()));
}
if (options.getExecutorService() != null) {
applicationBuilder.executorService(options.getExecutorService());
}
TokenCachePersistenceOptions tokenCachePersistenceOptions = options.getTokenCacheOptions();
PersistentTokenCacheImpl tokenCache = null;
if (tokenCachePersistenceOptions != null) {
try {
tokenCache = new PersistentTokenCacheImpl(enableCae)
.setAllowUnencryptedStorage(tokenCachePersistenceOptions.isUnencryptedStorageAllowed())
.setName(tokenCachePersistenceOptions.getName());
applicationBuilder.setTokenCacheAccessAspect(tokenCache);
} catch (Throwable t) {
throw LOGGER.logExceptionAsError(new ClientAuthenticationException(
"Shared token cache is unavailable in this environment.", null, t));
}
}
if (options.getRegionalAuthority() != null) {
if (options.getRegionalAuthority() == RegionalAuthority.AUTO_DISCOVER_REGION) {
applicationBuilder.autoDetectRegion(true);
} else {
applicationBuilder.azureRegion(options.getRegionalAuthority().toString());
}
}
ConfidentialClientApplication confidentialClientApplication = applicationBuilder.build();
if (tokenCache != null) {
tokenCache.registerCache();
}
return confidentialClientApplication;
}
PublicClientApplication getPublicClient(boolean sharedTokenCacheCredential, boolean enableCae) {
if (clientId == null) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException(
"A non-null value for client ID must be provided for user authentication."));
}
String authorityUrl
= TRAILING_FORWARD_SLASHES.matcher(options.getAuthorityHost()).replaceAll("") + "/" + tenantId;
PublicClientApplication.Builder builder = PublicClientApplication.builder(clientId);
try {
builder = builder.logPii(options.isUnsafeSupportLoggingEnabled())
.authority(authorityUrl)
.instanceDiscovery(options.isInstanceDiscoveryEnabled());
if (!options.isInstanceDiscoveryEnabled()) {
LOGGER.log(LogLevel.VERBOSE, () -> "Instance discovery and authority validation is disabled. In this"
+ " state, the library will not fetch metadata to validate the specified authority host. As a"
+ " result, it is crucial to ensure that the configured authority host is valid and trustworthy.");
}
} catch (MalformedURLException e) {
throw LOGGER.logExceptionAsWarning(new IllegalStateException(e));
}
initializeHttpPipelineAdapter();
if (httpPipelineAdapter != null) {
builder.httpClient(httpPipelineAdapter);
} else {
builder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions()));
}
if (options.getExecutorService() != null) {
builder.executorService(options.getExecutorService());
}
if (enableCae) {
Set set = new HashSet<>(1);
set.add("CP1");
builder.clientCapabilities(set);
}
if (options.isBrokerEnabled()) {
if (interactiveBrowserBroker == null) {
try {
interactiveBrowserBroker
= Class.forName("com.azure.identity.broker.implementation.InteractiveBrowserBroker");
} catch (ClassNotFoundException e) {
throw LOGGER.logExceptionAsError(
new IllegalStateException("Could not load the brokered authentication library. "
+ "Ensure that the azure-identity-broker library is on the classpath.", e));
}
getMsalRuntimeBroker = null;
try {
getMsalRuntimeBroker = interactiveBrowserBroker.getMethod("getMsalRuntimeBroker");
} catch (NoSuchMethodException e) {
throw LOGGER
.logExceptionAsError(new IllegalStateException("Could not obtain the InteractiveBrowserBroker. "
+ "Ensure that the azure-identity-broker library is on the classpath.", e));
}
}
try {
if (getMsalRuntimeBroker != null) {
builder.broker((IBroker) getMsalRuntimeBroker.invoke(null));
} else {
throw LOGGER.logExceptionAsError(new IllegalStateException("Could not obtain the MSAL Broker. "
+ "Ensure that the azure-identity-broker library is on the classpath.", null));
}
} catch (InvocationTargetException | IllegalAccessException e) {
throw LOGGER.logExceptionAsError(new IllegalStateException("Could not invoke the MSAL Broker. "
+ "Ensure that the azure-identity-broker library is on the classpath.", e));
}
}
TokenCachePersistenceOptions tokenCachePersistenceOptions = options.getTokenCacheOptions();
PersistentTokenCacheImpl tokenCache = null;
if (tokenCachePersistenceOptions != null) {
try {
tokenCache = new PersistentTokenCacheImpl(enableCae)
.setAllowUnencryptedStorage(tokenCachePersistenceOptions.isUnencryptedStorageAllowed())
.setName(tokenCachePersistenceOptions.getName());
builder.setTokenCacheAccessAspect(tokenCache);
} catch (Throwable t) {
throw LOGGER.logExceptionAsError(new ClientAuthenticationException(
"Shared token cache is unavailable in this environment.", null, t));
}
}
PublicClientApplication publicClientApplication = builder.build();
if (tokenCache != null) {
tokenCache.registerCache();
}
return publicClientApplication;
}
ConfidentialClientApplication getManagedIdentityConfidentialClient() {
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);
applicationBuilder.instanceDiscovery(false)
.validateAuthority(false)
.logPii(options.isUnsafeSupportLoggingEnabled());
try {
applicationBuilder = applicationBuilder.authority(authorityUrl);
} catch (MalformedURLException e) {
throw LOGGER.logExceptionAsWarning(new IllegalStateException(e));
}
if (options.getManagedIdentityType() == null) {
throw LOGGER.logExceptionAsError(new CredentialUnavailableException(
"Managed Identity type not configured, authentication not available."));
}
applicationBuilder.appTokenProvider(appTokenProviderParameters -> {
TokenRequestContext trc
= new TokenRequestContext().setScopes(new ArrayList<>(appTokenProviderParameters.scopes))
.setClaims(appTokenProviderParameters.claims)
.setTenantId(appTokenProviderParameters.tenantId);
Mono accessTokenAsync = getTokenFromTargetManagedIdentity(trc);
return accessTokenAsync.map(accessToken -> {
TokenProviderResult result = new TokenProviderResult();
result.setAccessToken(accessToken.getToken());
result.setTenantId(trc.getTenantId());
result.setExpiresInSeconds(accessToken.getExpiresAt().toEpochSecond());
if (accessToken.getRefreshAt() != null) {
result.setRefreshInSeconds(accessToken.getRefreshAt().toEpochSecond());
}
return result;
}).toFuture();
});
initializeHttpPipelineAdapter();
if (httpPipelineAdapter != null) {
applicationBuilder.httpClient(httpPipelineAdapter);
} else {
applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions()));
}
if (options.getExecutorService() != null) {
applicationBuilder.executorService(options.getExecutorService());
}
return applicationBuilder.build();
}
ManagedIdentityApplication getManagedIdentityMsalApplication() {
ManagedIdentityId managedIdentityId;
if (!CoreUtils.isNullOrEmpty(clientId)) {
managedIdentityId = ManagedIdentityId.userAssignedClientId(clientId);
} else if (!CoreUtils.isNullOrEmpty(resourceId)) {
managedIdentityId = ManagedIdentityId.userAssignedResourceId(resourceId);
} else if (!CoreUtils.isNullOrEmpty(objectId)) {
managedIdentityId = ManagedIdentityId.userAssignedObjectId(objectId);
} else {
managedIdentityId = ManagedIdentityId.systemAssigned();
}
ManagedIdentityApplication.Builder miBuilder
= ManagedIdentityApplication.builder(managedIdentityId).logPii(options.isUnsafeSupportLoggingEnabled());
ManagedIdentitySourceType managedIdentitySourceType = ManagedIdentityApplication.getManagedIdentitySource();
if (ManagedIdentitySourceType.DEFAULT_TO_IMDS.equals(managedIdentitySourceType)) {
options.setUseImdsRetryStrategy();
}
initializeHttpPipelineAdapter();
if (httpPipelineAdapter != null) {
miBuilder.httpClient(httpPipelineAdapter);
} else {
miBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions()));
}
if (options.getExecutorService() != null) {
miBuilder.executorService(options.getExecutorService());
}
return miBuilder.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)
.logPii(options.isUnsafeSupportLoggingEnabled())
.instanceDiscovery(options.isInstanceDiscoveryEnabled());
if (!options.isInstanceDiscoveryEnabled()) {
LOGGER.log(LogLevel.VERBOSE, () -> "Instance discovery and authority validation is disabled. In this"
+ " state, the library will not fetch metadata to validate the specified authority host. As a"
+ " result, it is crucial to ensure that the configured authority host is valid and trustworthy.");
}
} 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(new HashSet<>(request.getScopes()),
dc -> deviceCodeConsumer.accept(new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(),
OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message())))
.tenant(IdentityUtil.resolveTenantId(tenantId, request, options));
if (request.getClaims() != null) {
ClaimsRequest claimsRequest = ClaimsRequest.formatAsClaimsRequest(request.getClaims());
parametersBuilder.claims(claimsRequest);
}
return parametersBuilder;
}
OnBehalfOfParameters buildOBOFlowParameters(TokenRequestContext request) {
OnBehalfOfParameters.OnBehalfOfParametersBuilder builder
= OnBehalfOfParameters.builder(new HashSet<>(request.getScopes()), options.getUserAssertion())
.tenant(IdentityUtil.resolveTenantId(tenantId, request, options));
if (request.isCaeEnabled() && request.getClaims() != null) {
ClaimsRequest claimsRequest = ClaimsRequest.formatAsClaimsRequest(request.getClaims());
builder.claims(claimsRequest);
}
return builder.build();
}
InteractiveRequestParameters.InteractiveRequestParametersBuilder
buildInteractiveRequestParameters(TokenRequestContext request, String loginHint, URI redirectUri) {
InteractiveRequestParameters.InteractiveRequestParametersBuilder builder
= InteractiveRequestParameters.builder(redirectUri)
.scopes(new HashSet<>(request.getScopes()))
.prompt(Prompt.SELECT_ACCOUNT)
.tenant(IdentityUtil.resolveTenantId(tenantId, request, options));
if (request.isCaeEnabled() && request.getClaims() != null) {
ClaimsRequest claimsRequest = ClaimsRequest.formatAsClaimsRequest(request.getClaims());
builder.claims(claimsRequest);
}
BrowserCustomizationOptions browserCustomizationOptions = options.getBrowserCustomizationOptions();
if (IdentityUtil.browserCustomizationOptionsPresent(browserCustomizationOptions)) {
SystemBrowserOptions.SystemBrowserOptionsBuilder browserOptionsBuilder = SystemBrowserOptions.builder();
if (!CoreUtils.isNullOrEmpty(browserCustomizationOptions.getSuccessMessage())) {
browserOptionsBuilder.htmlMessageSuccess(browserCustomizationOptions.getSuccessMessage());
}
if (!CoreUtils.isNullOrEmpty(browserCustomizationOptions.getErrorMessage())) {
browserOptionsBuilder.htmlMessageError(browserCustomizationOptions.getErrorMessage());
}
builder.systemBrowserOptions(browserOptionsBuilder.build());
}
if (options.isBrokerEnabled()) {
builder.windowHandle(options.getBrokerWindowHandle());
if (options.isMsaPassthroughEnabled()) {
Map extraQueryParameters = new HashMap<>();
extraQueryParameters.put("msal_request_type", "consumer_passthrough");
builder.extraQueryParameters(extraQueryParameters);
}
if (request.getProofOfPossessionOptions() != null) {
ProofOfPossessionOptions proofOfPossessionOptions = request.getProofOfPossessionOptions();
try {
builder.proofOfPossession(
mapToMsalHttpMethod(proofOfPossessionOptions.getRequestMethod().toString()),
proofOfPossessionOptions.getRequestUrl().toURI(),
proofOfPossessionOptions.getProofOfPossessionNonce());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
}
if (loginHint != null) {
builder.loginHint(loginHint);
}
return builder;
}
static HttpMethod mapToMsalHttpMethod(String methodName) {
if (HTTP_METHOD_HASH_MAP.containsKey(methodName)) {
return HTTP_METHOD_HASH_MAP.get(methodName);
}
// Invalidate the cache if it grows too large. This is a simple cache and does not need to be large.
if (HTTP_METHOD_HASH_MAP.size() > 10) {
HTTP_METHOD_HASH_MAP.clear();
}
for (HttpMethod method : HttpMethod.values()) {
if (method.methodName.equalsIgnoreCase(methodName)) {
HTTP_METHOD_HASH_MAP.put(methodName, method);
return method;
}
}
throw new IllegalArgumentException("No enum constant with method name: " + methodName);
}
UserNamePasswordParameters.UserNamePasswordParametersBuilder
buildUsernamePasswordFlowParameters(TokenRequestContext request, String username, String password) {
UserNamePasswordParameters.UserNamePasswordParametersBuilder userNamePasswordParametersBuilder
= UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray());
if (request.isCaeEnabled() && request.getClaims() != null) {
ClaimsRequest claimsRequest = ClaimsRequest.formatAsClaimsRequest(request.getClaims());
userNamePasswordParametersBuilder.claims(claimsRequest);
}
userNamePasswordParametersBuilder.tenant(IdentityUtil.resolveTenantId(tenantId, request, options));
return userNamePasswordParametersBuilder;
}
AccessToken getTokenFromAzureCLIAuthentication(StringBuilder azCommand) {
AccessToken token;
try {
String starter;
String switcher;
if (isWindowsPlatform()) {
starter = WINDOWS_STARTER;
switcher = WINDOWS_SWITCHER;
} else {
starter = LINUX_MAC_STARTER;
switcher = LINUX_MAC_SWITCHER;
}
ProcessBuilder builder = new ProcessBuilder(starter, switcher, azCommand.toString());
// Redirects stdin to dev null, helps to avoid messages sent in by the cmd process to upgrade etc.
builder.redirectInput(ProcessBuilder.Redirect.from(IdentityUtil.NULL_FILE));
String workingDirectory = getSafeWorkingDirectory();
if (workingDirectory != null) {
builder.directory(new File(workingDirectory));
} else {
throw LOGGER.logExceptionAsError(new IllegalStateException("A Safe Working directory could not be"
+ " found to execute CLI command from. To mitigate this issue, please refer to the troubleshooting "
+ " guidelines here at https://aka.ms/azsdk/java/identity/azclicredential/troubleshoot"));
}
builder.redirectErrorStream(true);
Process process = builder.start();
StringBuilder output = new StringBuilder();
try (BufferedReader reader
= new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while (true) {
line = reader.readLine();
if (line == null) {
break;
}
if (WINDOWS_PROCESS_ERROR_MESSAGE.matcher(line).find()
|| SH_PROCESS_ERROR_MESSAGE.matcher(line).find()) {
throw LoggingUtil.logCredentialUnavailableException(LOGGER, options,
new CredentialUnavailableException(
"AzureCliCredential authentication unavailable. Azure CLI not installed."
+ "To mitigate this issue, please refer to the troubleshooting guidelines here at "
+ "https://aka.ms/azsdk/java/identity/azclicredential/troubleshoot"));
}
output.append(line);
}
}
String processOutput = output.toString();
process.waitFor(this.options.getCredentialProcessTimeout().getSeconds(), TimeUnit.SECONDS);
if (process.exitValue() != 0) {
if (processOutput.length() > 0) {
String redactedOutput = redactInfo(processOutput);
if (redactedOutput.contains("az login") || redactedOutput.contains("az account set")) {
throw LoggingUtil.logCredentialUnavailableException(LOGGER, options,
new CredentialUnavailableException("AzureCliCredential authentication unavailable."
+ " Please run 'az login' to set up account. To further mitigate this"
+ " issue, please refer to the troubleshooting guidelines here at "
+ "https://aka.ms/azsdk/java/identity/azclicredential/troubleshoot"));
}
throw LOGGER.logExceptionAsError(new ClientAuthenticationException(redactedOutput, null));
} else {
throw LOGGER
.logExceptionAsError(new ClientAuthenticationException("Failed to invoke Azure CLI ", null));
}
}
LOGGER.verbose("Azure CLI Authentication => A token response was received from Azure CLI, deserializing the"
+ " response into an Access Token.");
try (JsonReader reader = JsonProviders.createReader(processOutput)) {
AzureCliToken tokenHolder = AzureCliToken.fromJson(reader);
String accessToken = tokenHolder.getAccessToken();
OffsetDateTime tokenExpiration = tokenHolder.getTokenExpiration();
token = new AccessToken(accessToken, tokenExpiration);
}
} catch (IOException | InterruptedException e) {
IllegalStateException ex = new IllegalStateException(redactInfo(e.getMessage()));
ex.setStackTrace(e.getStackTrace());
throw LOGGER.logExceptionAsError(ex);
}
return token;
}
AccessToken getTokenFromAzureDeveloperCLIAuthentication(StringBuilder azdCommand) {
AccessToken token;
try {
String starter;
String switcher;
if (isWindowsPlatform()) {
starter = WINDOWS_STARTER;
switcher = WINDOWS_SWITCHER;
} else {
starter = LINUX_MAC_STARTER;
switcher = LINUX_MAC_SWITCHER;
}
ProcessBuilder builder = new ProcessBuilder(starter, switcher, azdCommand.toString());
// Redirects stdin to dev null, helps to avoid messages sent in by the cmd process to upgrade etc.
builder.redirectInput(ProcessBuilder.Redirect.from(IdentityUtil.NULL_FILE));
String workingDirectory = getSafeWorkingDirectory();
if (workingDirectory != null) {
builder.directory(new File(workingDirectory));
} else {
throw LOGGER.logExceptionAsError(new IllegalStateException(
"A Safe Working directory could not be" + " found to execute Azure Developer CLI command from."));
}
builder.redirectErrorStream(true);
Process process = builder.start();
StringBuilder output = new StringBuilder();
try (BufferedReader reader
= new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8.name()))) {
String line;
while (true) {
line = reader.readLine();
if (line == null) {
break;
}
if (WINDOWS_PROCESS_ERROR_MESSAGE.matcher(line).find()
|| SH_PROCESS_ERROR_MESSAGE.matcher(line).find()) {
throw LoggingUtil.logCredentialUnavailableException(LOGGER, options,
new CredentialUnavailableException(
"AzureDeveloperCliCredential authentication unavailable. Azure Developer CLI not installed."
+ "To mitigate this issue, please refer to the troubleshooting guidelines here at "
+ "https://aka.ms/azsdk/java/identity/azdevclicredential/troubleshoot"));
}
output.append(line);
}
}
String processOutput = output.toString();
// wait until the process completes or the timeout (10 sec) is reached.
process.waitFor(this.options.getCredentialProcessTimeout().getSeconds(), TimeUnit.SECONDS);
if (process.exitValue() != 0) {
if (processOutput.length() > 0) {
String redactedOutput = redactInfo(processOutput);
if (redactedOutput.contains("azd auth login") || redactedOutput.contains("not logged in")) {
throw LoggingUtil.logCredentialUnavailableException(LOGGER, options,
new CredentialUnavailableException("AzureDeveloperCliCredential authentication unavailable."
+ " Please run 'azd auth login' to set up account."));
}
throw LOGGER.logExceptionAsError(new ClientAuthenticationException(redactedOutput, null));
} else {
throw LOGGER.logExceptionAsError(
new ClientAuthenticationException("Failed to invoke Azure Developer CLI ", null));
}
}
LOGGER.verbose(
"Azure Developer CLI Authentication => A token response was received from Azure Developer CLI, deserializing the"
+ " response into an Access Token.");
try (JsonReader reader = JsonProviders.createReader(processOutput)) {
reader.nextToken();
Map objectMap = reader.readMap(JsonReader::getString);
String accessToken = objectMap.get("token");
String time = objectMap.get("expiresOn");
// az expiresOn format = "2022-11-30 02:38:42.000000" vs
// azd expiresOn format = "2022-11-30T02:05:08Z"
String standardTime = time.substring(0, time.indexOf("Z"));
OffsetDateTime expiresOn = LocalDateTime.parse(standardTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.atZone(ZoneId.of("Z"))
.toOffsetDateTime()
.withOffsetSameInstant(ZoneOffset.UTC);
token = new AccessToken(accessToken, expiresOn);
}
} catch (IOException | InterruptedException e) {
IllegalStateException ex = new IllegalStateException(redactInfo(e.getMessage()));
ex.setStackTrace(e.getStackTrace());
throw LOGGER.logExceptionAsError(ex);
}
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 MSIToken.fromJson(JsonProviders.createReader(connection.getInputStream()));
} catch (IOException exception) {
if (connection == null) {
throw LOGGER.logExceptionAsError(
new RuntimeException("Could not connect to the authority host: " + url + ".", exception));
}
int responseCode;
try {
responseCode = connection.getResponseCode();
} catch (Exception e) {
throw LoggingUtil
.logCredentialUnavailableException(LOGGER, options,
new CredentialUnavailableException(
"WorkloadIdentityCredential authentication unavailable. "
+ "Connection to the authority host cannot be established, " + e.getMessage() + ".",
e));
}
if (responseCode == 400) {
throw LoggingUtil.logCredentialUnavailableException(LOGGER, options,
new CredentialUnavailableException("WorkloadIdentityCredential authentication unavailable. "
+ "The request to the authority host was invalid. " + "Additional details: "
+ exception.getMessage() + ".", exception));
}
throw LOGGER.logExceptionAsError(
new RuntimeException("Couldn't acquire access token from Workload Identity.", exception));
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
String getSafeWorkingDirectory() {
if (isWindowsPlatform()) {
String windowsSystemRoot = System.getenv("SystemRoot");
if (CoreUtils.isNullOrEmpty(windowsSystemRoot)) {
return null;
}
return windowsSystemRoot + "\\system32";
} else {
return DEFAULT_MAC_LINUX_PATH;
}
}
String redactInfo(String input) {
return ACCESS_TOKEN_PATTERN.matcher(input).replaceAll("****");
}
abstract Mono getTokenFromTargetManagedIdentity(TokenRequestContext tokenRequestContext);
HttpPipeline setupPipeline() {
List policies = new ArrayList<>();
String clientName = properties.getOrDefault(SDK_NAME, "UnknownName");
String clientVersion = properties.getOrDefault(SDK_VERSION, "UnknownVersion");
Configuration buildConfiguration = Configuration.getGlobalConfiguration().clone();
HttpLogOptions httpLogOptions
= (options.getHttpLogOptions() == null) ? new HttpLogOptions() : options.getHttpLogOptions();
ClientOptions localClientOptions
= options.getClientOptions() != null ? options.getClientOptions() : DEFAULT_CLIENT_OPTIONS;
userAgent = UserAgentUtil.toUserAgentString(CoreUtils.getApplicationId(localClientOptions, httpLogOptions),
clientName, clientVersion, buildConfiguration);
policies.add(new UserAgentPolicy(userAgent));
List httpHeaderList = new ArrayList<>();
localClientOptions.getHeaders()
.forEach(header -> httpHeaderList.add(new HttpHeader(header.getName(), header.getValue())));
policies.add(new AddHeadersPolicy(new HttpHeaders(httpHeaderList)));
policies.addAll(options.getPerCallPolicies());
HttpPolicyProviders.addBeforeRetryPolicies(policies);
// Add retry policy.
RetryPolicy retryPolicy = options.getRetryPolicy();
if (retryPolicy == null && options.getUseImdsRetryStrategy()) {
retryPolicy = new RetryPolicy(new ImdsRetryStrategy());
}
policies.add(ClientBuilderUtil.validateAndGetRetryPolicy(retryPolicy, options.getRetryOptions()));
policies.addAll(options.getPerRetryPolicies());
HttpPolicyProviders.addAfterRetryPolicies(policies);
policies.add(new HttpLoggingPolicy(httpLogOptions));
// if the user has not supplied an httpClient, the builder will create a default using localClientOptions.
// If the user has supplied an HttpClient, it will be used as is.
return new HttpPipelineBuilder().httpClient(options.getHttpClient())
.clientOptions(localClientOptions)
.policies(policies.toArray(new HttpPipelinePolicy[0]))
.build();
}
void initializeHttpPipelineAdapter() {
if (options.getProxyOptions() == null) {
httpPipelineAdapter = new HttpPipelineAdapter(getPipeline(), options);
}
}
HttpPipeline getPipeline() {
// if we've already initialized, return the pipeline
if (this.httpPipeline != null) {
return httpPipeline;
}
// if the user has supplied a pipeline, use it
HttpPipeline httpPipeline = options.getHttpPipeline();
if (httpPipeline != null) {
this.httpPipeline = httpPipeline;
return this.httpPipeline;
}
// setupPipeline will use the user's HttpClient and HttpClientOptions if they're set
// otherwise it will use defaults.
this.httpPipeline = setupPipeline();
return this.httpPipeline;
}
private byte[] getCertificateBytes() throws IOException {
if (certificatePath != null) {
return Files.readAllBytes(Paths.get(certificatePath));
} else if (certificate != null) {
return certificate;
} else {
return new byte[0];
}
}
private InputStream getCertificateInputStream() throws IOException {
if (certificatePath != null) {
return new BufferedInputStream(new FileInputStream(certificatePath));
} else {
return new ByteArrayInputStream(certificate);
}
}
private static Proxy proxyOptionsToJavaNetProxy(ProxyOptions options) {
switch (options.getType()) {
case SOCKS4:
case SOCKS5:
return new Proxy(Proxy.Type.SOCKS, options.getAddress());
case HTTP:
default:
return new Proxy(Proxy.Type.HTTP, options.getAddress());
}
}
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.
*
* @return the tenant id.
*/
public String getTenantId() {
return tenantId;
}
/**
* Get the configured client id.
*
* @return the client id.
*/
public String getClientId() {
return clientId;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy