All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.microsoft.commondatamodel.objectmodel.storage.AdlsAdapterAuthenticator Maven / Gradle / Ivy
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
package com.microsoft.commondatamodel.objectmodel.storage;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.IClientCredential;
import com.microsoft.commondatamodel.objectmodel.enums.AzureCloudEndpointConvertor;
import com.microsoft.commondatamodel.objectmodel.utilities.StringUtils;
import com.microsoft.commondatamodel.objectmodel.utilities.network.TokenProvider;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Collections;
import java.util.Set;
/**
* AdlsAdapterAuthenticator handles authentication items for ADLS requests.
*/
class AdlsAdapterAuthenticator {
private static final String HMAC_SHA256 = "HmacSHA256";
// The authorization header key, used during shared key auth.
private static final String HTTP_AUTHORIZATION = "Authorization";
// The MS date header key, used during shared key auth.
private static final String HTTP_XMS_DATE = "x-ms-date";
// The MS version key, used during shared key auth.
private static final String HTTP_XMS_VERSION = "x-ms-version";
// The default scope.
private final static Set SCOPE = Collections.singleton("https://storage.azure.com/.default");
private String sharedKey;
private String tenant;
private String clientId;
private String secret;
private ConfidentialClientApplication context;
private String sasToken;
private IAuthenticationResult lastAuthenticationResult;
private TokenProvider tokenProvider;
private com.microsoft.commondatamodel.objectmodel.enums.AzureCloudEndpoint endpoint;
AdlsAdapterAuthenticator() {
this.sharedKey = null;
this.clientId = null;
this.secret = null;
this.tenant = null;
this.tokenProvider = null;
this.endpoint = null;
}
/**
* Build a ADLS request's authentication header
* @param url The url of the request
* @param method The method of the request
* @param content The content of the request
* @param contentType The contentType of the request
* @return The authentication headers
*/
Map buildAuthenticationHeader(
final String url,
final String method,
final String content,
final String contentType)
throws NoSuchAlgorithmException, InvalidKeyException, URISyntaxException, UnsupportedEncodingException {
if (sharedKey != null) {
return buildAuthenticationHeaderWithSharedKey(url, method, content, contentType);
} else if (tokenProvider != null) {
final Map header = new LinkedHashMap<>();
header.put("authorization", tokenProvider.getToken());
return header;
} else if (clientId != null && tenant != null && secret != null) {
return buildAuthenticationHeaderWithClientIdAndSecret();
} else {
throw new StorageAdapterException("ADLS adapter is not configured with any auth method");
}
}
/**
* Appends SAS token to the given URL.
* @param url URL to be appended with the SAS token
* @return URL with the SAS token appended
*/
String buildSasAuthenticatedUrl(String url) {
return url + (url.contains("?") ? "&" : "?") + sasToken;
}
/**
* Returns the authentication headers with the applied shared key.
* @param url The URL with query parameters sorted lexicographically.
* @param method The HTTP method.
* @param content The string content.
* @param contentType The content type.
* @return The authentication headers
*/
private Map buildAuthenticationHeaderWithSharedKey(
final String url,
final String method,
final String content,
final String contentType)
throws URISyntaxException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
final Map headers = new LinkedHashMap<>();
// Add UTC now time and new version.
headers.put(HTTP_XMS_DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.ofHours(0))));
headers.put(HTTP_XMS_VERSION, "2018-06-17");
int contentLength = 0;
if (content != null) {
contentLength = content.getBytes().length;
}
final URI uri = new URI(url);
final StringBuilder builder = new StringBuilder();
builder.append(method).append("\n"); // Verb.yi
builder.append("\n"); // Content-Encoding.
builder.append("\n"); // Content-Language.
builder.append((contentLength != 0) ? contentLength : "").append("\n"); // Content length.
builder.append("\n"); // Content-md5.
builder.append(contentType != null ? contentType : "").append("\n"); // Content-type.
builder.append("\n"); // Date.
builder.append("\n"); // If-modified-since.
builder.append("\n"); // If-match.
builder.append("\n"); // If-none-match.
builder.append("\n"); // If-unmodified-since.
builder.append("\n"); // Range.
for (final Map.Entry header : headers.entrySet()) {
builder.append(header.getKey()).append(":").append(header.getValue()).append("\n");
}
// Append canonicalized resource.
final String accountName = uri.getHost().split("\\.")[0];
builder.append("/").append(accountName);
builder.append(uri.getRawPath());
// Append canonicalized queries.
if (!StringUtils.isNullOrEmpty(uri.getQuery())) {
final String queryParameters = uri.getRawQuery();
final String[] queryParts = queryParameters.split("&");
for(final String item : queryParts) {
final String[] keyValuePair = item.split("=");
String queryName = keyValuePair[0].toLowerCase();
String decodedValue = URLDecoder.decode(keyValuePair[1], "UTF-8");
builder.append("\n").append(queryName).append(":").append(decodedValue);
}
}
final Mac sha256_HMAC = Mac.getInstance(HMAC_SHA256);
final SecretKeySpec secret_key = new SecretKeySpec(Base64.decodeBase64(sharedKey.getBytes()), HMAC_SHA256);
sha256_HMAC.init(secret_key);
final String hash = Base64.encodeBase64String(sha256_HMAC.doFinal(builder.toString().getBytes(StandardCharsets.UTF_8)));
headers.put(HTTP_AUTHORIZATION, "SharedKey " + accountName + ":" + hash);
return headers;
}
/**
* Build a ADLS request's authentication header with clientId and secret
* @return The authentication header
*/
private Map buildAuthenticationHeaderWithClientIdAndSecret() {
final Map header = new LinkedHashMap<>();
if (this.needsRefreshToken()) {
this.refreshToken();
}
header.put("authorization", "Bearer " + this.lastAuthenticationResult.accessToken());
return header;
}
/**
* If need to refresh authentication token or not.
* @return If need to refresh authentication token or not.
*/
private boolean needsRefreshToken() {
if (lastAuthenticationResult == null) {
return true;
}
Date now = new Date();
return this.lastAuthenticationResult.expiresOnDate().before(now);
}
/**
* Refresh the authentication token.
*/
private void refreshToken() {
this.buildContext();
IAuthenticationResult result;
try {
ClientCredentialParameters parameters = ClientCredentialParameters.builder(SCOPE).build();
result = this.context.acquireToken(parameters).join();
} catch (Exception ex) {
String errorMsg = ex.getLocalizedMessage();
if (ex.getCause() != null) {
errorMsg += " InnerException: " + ex.getCause().getLocalizedMessage();
}
throw new StorageAdapterException("There was an error while acquiring ADLS Adapter's Token with client ID/secret authentication. " + errorMsg);
}
if (result == null || result.accessToken() == null) {
throw new StorageAdapterException("Received invalid ADLS Adapter's authentication result. The result might be null, or missing access token from the authentication result.");
}
this.lastAuthenticationResult = result;
}
/**
* Build context when users make the first call. Also need to ensure client Id, tenant and secret are not null.
*/
private void buildContext() {
if (this.context == null) {
IClientCredential credential = ClientCredentialFactory.createFromSecret(this.secret);
try {
this.context = ConfidentialClientApplication
.builder(this.clientId, credential)
.authority(AzureCloudEndpointConvertor.azureCloudEndpointToInstance(this.endpoint).endpoint + this.tenant)
.build();
} catch (MalformedURLException e) {
throw new StorageAdapterException("There was an error while building context. Exception: ", e);
}
}
}
String getSharedKey() {
return sharedKey;
}
void setSharedKey(String sharedKey) {
this.sharedKey = sharedKey;
}
String getTenant() {
return tenant;
}
void setTenant(String tenant) {
this.tenant = tenant;
}
String getClientId() {
return clientId;
}
void setClientId(String clientId) {
this.clientId = clientId;
}
String getSecret() {
return secret;
}
void setSecret(String secret) {
this.secret = secret;
}
String getSasToken() {
return sasToken;
}
void setEndpoint(final com.microsoft.commondatamodel.objectmodel.enums.AzureCloudEndpoint endpoint) {
this.endpoint = endpoint;
}
com.microsoft.commondatamodel.objectmodel.enums.AzureCloudEndpoint getEndpoint() {
return endpoint;
}
/**
* Sets the SAS token. If supplied string begins with '?' symbol, the symbol gets stripped away.
* @param sasToken The SAS token
*/
void setSasToken(String sasToken) {
this.sasToken = sasToken != null ?
(sasToken.startsWith("?") ? sasToken.substring(1) : sasToken)
: null;
}
TokenProvider getTokenProvider() {
return tokenProvider;
}
void setTokenProvider(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
}