mirror of https://github.com/apache/jclouds.git
implement support for SharedKey signature (#186)
* implement support for SharedKey signature This is the recommended signature scheme for Azure, and the only scheme that is supported by the Azurite emulator. https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key * Remove print statement Co-authored-by: Ignasi Barrera <nacx@apache.org> * Remove print statement * simplify logic --------- Co-authored-by: Ignasi Barrera <nacx@apache.org>
This commit is contained in:
parent
eb1181daf5
commit
a2628f9cbf
|
@ -272,6 +272,10 @@ public class HttpUtils {
|
|||
return md5 != null ? base64().encode(md5) : "";
|
||||
}
|
||||
|
||||
public static String nullOrZeroToEmpty(Long contentLength) {
|
||||
return contentLength != null && contentLength > 0 ? contentLength.toString() : "";
|
||||
}
|
||||
|
||||
public static String nullToEmpty(Collection<String> collection) {
|
||||
return (collection == null || collection.isEmpty()) ? "" : collection.iterator().next();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@ public enum AuthType {
|
|||
/** Includes both the API key and SAS credentials */
|
||||
AZURE_KEY,
|
||||
/** Azure AD credentials */
|
||||
AZURE_AD;
|
||||
AZURE_AD,
|
||||
/** Uses the SharedKey scheme, rather than SharedKeyLite */
|
||||
AZURE_SHARED_KEY;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
|
|
@ -34,6 +34,14 @@ import javax.inject.Named;
|
|||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.collect.TreeMultiset;
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.azure.storage.config.AuthType;
|
||||
import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier;
|
||||
|
@ -47,6 +55,8 @@ import org.jclouds.http.HttpUtils;
|
|||
import org.jclouds.http.Uris;
|
||||
import org.jclouds.http.Uris.UriBuilder;
|
||||
import org.jclouds.http.internal.SignatureWire;
|
||||
import org.jclouds.io.ContentMetadata;
|
||||
import org.jclouds.io.Payload;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.oauth.v2.filters.OAuthFilter;
|
||||
import org.jclouds.util.Strings2;
|
||||
|
@ -56,13 +66,7 @@ import com.google.common.base.Function;
|
|||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMap.Builder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.io.ByteProcessor;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
|
@ -74,6 +78,9 @@ import com.google.common.net.HttpHeaders;
|
|||
@Singleton
|
||||
public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
||||
private static final Collection<String> FIRST_HEADERS_TO_SIGN = ImmutableList.of(HttpHeaders.DATE);
|
||||
private static final Collection<String> FIRST_HEADERS_TO_SIGN_FOR_SHARED_KEY =
|
||||
ImmutableList.of(HttpHeaders.DATE, HttpHeaders.IF_MODIFIED_SINCE, HttpHeaders.IF_MATCH,
|
||||
HttpHeaders.IF_NONE_MATCH, HttpHeaders.IF_UNMODIFIED_SINCE, HttpHeaders.RANGE);
|
||||
private final SignatureWire signatureWire;
|
||||
private final Supplier<Credentials> creds;
|
||||
private final Provider<String> timeStampProvider;
|
||||
|
@ -114,6 +121,8 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
|||
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||
if (this.authType == AuthType.AZURE_AD) {
|
||||
request = this.oAuthFilter.filter(request);
|
||||
} else if (this.authType == AuthType.AZURE_SHARED_KEY){
|
||||
request = this.isSAS ? filterSAS(request, this.credential) : filterSharedKey(request);
|
||||
} else {
|
||||
request = this.isSAS ? filterSAS(request, this.credential) : filterKey(request);
|
||||
}
|
||||
|
@ -154,6 +163,21 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
|||
return replaceAuthorizationHeader(request, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* this is a 'standard' filter method, applied when SharedKey authentication is used.
|
||||
*/
|
||||
public HttpRequest filterSharedKey(HttpRequest request) throws HttpException {
|
||||
request = replaceDateHeader(request);
|
||||
String signature = calculateSignature(createStringToSignForSharedKey(request));
|
||||
return replaceAuthorizationHeaderForSharedKey(request, signature);
|
||||
}
|
||||
|
||||
HttpRequest replaceAuthorizationHeaderForSharedKey(HttpRequest request, String signature) {
|
||||
return request.toBuilder()
|
||||
.replaceHeader(HttpHeaders.AUTHORIZATION, "SharedKey " + creds.get().identity + ":" + signature)
|
||||
.build();
|
||||
}
|
||||
|
||||
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
|
||||
return request.toBuilder()
|
||||
.replaceHeader(HttpHeaders.AUTHORIZATION, "SharedKeyLite " + creds.get().identity + ":" + signature)
|
||||
|
@ -189,6 +213,20 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
|||
return result;
|
||||
}
|
||||
|
||||
public String createStringToSignForSharedKey(HttpRequest request) {
|
||||
utils.logRequest(signatureLog, request, ">>");
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
// re-sign the request
|
||||
appendMethod(request, buffer);
|
||||
appendPayloadMetadataForSharedKey(request, buffer);
|
||||
appendHttpHeadersForSharedKey(request, buffer);
|
||||
appendCanonicalizedHeaders(request, buffer);
|
||||
appendCanonicalizedResourceForSharedKey(request, buffer);
|
||||
if (signatureWire.enabled())
|
||||
signatureWire.output(buffer.toString());
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String createStringToSign(HttpRequest request) {
|
||||
utils.logRequest(signatureLog, request, ">>");
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
@ -203,6 +241,26 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
|||
return buffer.toString();
|
||||
}
|
||||
|
||||
private void appendPayloadMetadataForSharedKey(HttpRequest request, StringBuilder buffer) {
|
||||
Payload payload = request.getPayload();
|
||||
if (payload == null) {
|
||||
buffer.append("\n\n\n\n\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ContentMetadata contentMetadata = payload.getContentMetadata();
|
||||
buffer.append(Strings.nullToEmpty(contentMetadata.getContentEncoding()))
|
||||
.append("\n");
|
||||
buffer.append(Strings.nullToEmpty(contentMetadata.getContentLanguage()))
|
||||
.append("\n");
|
||||
buffer.append(HttpUtils.nullOrZeroToEmpty(contentMetadata.getContentLength()))
|
||||
.append("\n");
|
||||
buffer.append(HttpUtils.nullToEmpty(contentMetadata.getContentMD5()))
|
||||
.append("\n");
|
||||
buffer.append(Strings.nullToEmpty(contentMetadata.getContentType()))
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
private void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) {
|
||||
buffer.append(
|
||||
HttpUtils.nullToEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
|
||||
|
@ -260,6 +318,11 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
|||
toSign.append(HttpUtils.nullToEmpty(request.getHeaders().get(header))).append("\n");
|
||||
}
|
||||
|
||||
private void appendHttpHeadersForSharedKey(HttpRequest request, StringBuilder toSign) {
|
||||
for (String header : FIRST_HEADERS_TO_SIGN_FOR_SHARED_KEY)
|
||||
toSign.append(HttpUtils.nullToEmpty(request.getHeaders().get(header))).append("\n");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void appendCanonicalizedResource(HttpRequest request, StringBuilder toSign) {
|
||||
// 1. Beginning with an empty string (""), append a forward slash (/), followed by the name of
|
||||
|
@ -268,6 +331,51 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
|||
appendUriPath(request, toSign);
|
||||
}
|
||||
|
||||
void appendCanonicalizedResourceForSharedKey(HttpRequest request, StringBuilder toSign) {
|
||||
// 1. Beginning with an empty string (""), append a forward slash (/), followed by the name of
|
||||
// the identity that owns the resource being accessed.
|
||||
toSign.append("/").append(creds.get().identity);
|
||||
// 2. Append the resource's encoded URI path
|
||||
toSign.append(request.getEndpoint().getRawPath());
|
||||
appendQueryParametersForSharedKey(request, toSign);
|
||||
}
|
||||
|
||||
void appendQueryParametersForSharedKey(HttpRequest request, StringBuilder toSign) {
|
||||
// 3. Append each query parameter as a new line
|
||||
Map<String, Multiset<String>> sortedParams = Maps.newTreeMap();
|
||||
if (request.getEndpoint().getQuery() != null) {
|
||||
String[] params = request.getEndpoint().getQuery().split("&");
|
||||
for (String param : params) {
|
||||
String[] paramNameAndValue = param.split("=");
|
||||
String key = paramNameAndValue[0];
|
||||
String value = paramNameAndValue.length > 1 ? paramNameAndValue[1] : "";
|
||||
if (sortedParams.containsKey(key)) {
|
||||
sortedParams.get(key).add(value);
|
||||
} else {
|
||||
Multiset<String> values = TreeMultiset.create();
|
||||
values.add(value);
|
||||
sortedParams.put(key, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<String, Multiset<String>> entry : sortedParams.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Multiset<String> values = entry.getValue();
|
||||
toSign.append("\n");
|
||||
toSign.append(key);
|
||||
toSign.append(":");
|
||||
boolean first = true;
|
||||
for (String value : values) {
|
||||
if (!first) {
|
||||
toSign.append(",");
|
||||
}
|
||||
toSign.append(value);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void appendUriPath(HttpRequest request, StringBuilder toSign) {
|
||||
// 2. Append the resource's encoded URI path
|
||||
|
|
Loading…
Reference in New Issue