This is an automated email from the ASF dual-hosted git repository.
nacx pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jclouds.git
The following commit(s) were added to refs/heads/master by this push:
new a2628f9cbf implement support for SharedKey signature (#186)
a2628f9cbf is described below
commit a2628f9cbf46519f88600883b23e8819b18e3724
Author: Lars Hagen <[email protected]>
AuthorDate: Fri Oct 20 11:19:06 2023 +0200
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 <[email protected]>
* Remove print statement
* simplify logic
---------
Co-authored-by: Ignasi Barrera <[email protected]>
---
core/src/main/java/org/jclouds/http/HttpUtils.java | 4 +
.../org/jclouds/azure/storage/config/AuthType.java | 4 +-
.../filters/SharedKeyLiteAuthentication.java | 124 +++++++++++++++++++--
3 files changed, 123 insertions(+), 9 deletions(-)
diff --git a/core/src/main/java/org/jclouds/http/HttpUtils.java
b/core/src/main/java/org/jclouds/http/HttpUtils.java
index 0d41d7c83f..eb36a50c12 100644
--- a/core/src/main/java/org/jclouds/http/HttpUtils.java
+++ b/core/src/main/java/org/jclouds/http/HttpUtils.java
@@ -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();
}
diff --git
a/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java
b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java
index 986f21dfc6..fd3b2ee05e 100644
---
a/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java
+++
b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java
@@ -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() {
diff --git
a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
index 2d8f3b62e4..cfa2d4997e 100644
---
a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
+++
b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java
@@ -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);
}
@@ -153,7 +162,22 @@ public class SharedKeyLiteAuthentication implements
HttpRequestFilter {
String signature = calculateSignature(createStringToSign(request));
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)
@@ -187,7 +211,21 @@ public class SharedKeyLiteAuthentication implements
HttpRequestFilter {
throw new IllegalArgumentException("there is neither ContainerName
nor BlobName in the URI path");
}
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, ">>");
@@ -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