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 <lars.ha...@cognite.com>
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 <n...@apache.org>
    
    * Remove print statement
    
    * simplify logic
    
    ---------
    
    Co-authored-by: Ignasi Barrera <n...@apache.org>
---
 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

Reply via email to