From 6788aad8c795871f3778c4c75da068920311a205 Mon Sep 17 00:00:00 2001
From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com>
Date: Fri, 14 Jun 2019 12:13:39 -0700
Subject: [PATCH] Azure Storage Common Credentials and Policies (#3909)
Adds common credentials and policies to Azure Storage client and changes AutoREST Impl files with handwritten overrides until AutoREST codegen is updated.
---
pom.client.xml | 1 +
storage/client/pom.xml | 21 +-
.../blob/implementation/AppendBlobsImpl.java | 9 +-
.../blob/implementation/BlobsImpl.java | 7 +-
.../blob/implementation/BlockBlobsImpl.java | 9 +-
.../blob/implementation/ContainersImpl.java | 12 +-
.../blob/implementation/PageBlobsImpl.java | 9 +-
.../blob/implementation/ServicesImpl.java | 2 +
.../credentials/SASTokenCredential.java | 96 +++++++++
.../credentials/SharedKeyCredential.java | 185 ++++++++++++++++++
.../common/credentials/package-info.java | 7 +
.../policy/SASTokenCredentialPolicy.java | 45 +++++
.../policy/SharedKeyCredentialPolicy.java | 35 ++++
.../storage/common/policy/package-info.java | 7 +
.../file/implementation/DirectorysImpl.java | 5 +-
.../file/implementation/FilesImpl.java | 5 +-
.../file/implementation/ServicesImpl.java | 8 +-
.../file/implementation/SharesImpl.java | 5 +-
.../queue/implementation/MessageIdsImpl.java | 2 +
.../queue/implementation/MessagesImpl.java | 2 +
.../queue/implementation/QueuesImpl.java | 5 +-
.../queue/implementation/ServicesImpl.java | 8 +-
.../com/azure/storage/blob/BlobPocTests.java | 3 +-
23 files changed, 461 insertions(+), 27 deletions(-)
create mode 100644 storage/client/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java
create mode 100644 storage/client/src/main/java/com/azure/storage/common/credentials/SharedKeyCredential.java
create mode 100644 storage/client/src/main/java/com/azure/storage/common/credentials/package-info.java
create mode 100644 storage/client/src/main/java/com/azure/storage/common/policy/SASTokenCredentialPolicy.java
create mode 100644 storage/client/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java
create mode 100644 storage/client/src/main/java/com/azure/storage/common/policy/package-info.java
diff --git a/pom.client.xml b/pom.client.xml
index 8cd8b91a4e8d9..619abfc3b86f7 100644
--- a/pom.client.xml
+++ b/pom.client.xml
@@ -683,6 +683,7 @@
./appconfiguration/client
./core
./keyvault/client
+ ./storage/client
./tracing
./identity/client
diff --git a/storage/client/pom.xml b/storage/client/pom.xml
index 1343bc3b168e7..97074511bf21d 100644
--- a/storage/client/pom.xml
+++ b/storage/client/pom.xml
@@ -75,8 +75,25 @@
org.apache.maven.plugins
maven-checkstyle-plugin
- true
- true
+ false
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+ false
+ false
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+ false
diff --git a/storage/client/src/main/java/com/azure/storage/blob/implementation/AppendBlobsImpl.java b/storage/client/src/main/java/com/azure/storage/blob/implementation/AppendBlobsImpl.java
index 06a4683ee3b84..eea979ef28e44 100644
--- a/storage/client/src/main/java/com/azure/storage/blob/implementation/AppendBlobsImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/blob/implementation/AppendBlobsImpl.java
@@ -9,9 +9,10 @@
import com.azure.core.annotations.HeaderParam;
import com.azure.core.annotations.Host;
import com.azure.core.annotations.HostParam;
-import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.PUT;
+import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.DateTimeRfc1123;
import com.azure.core.implementation.RestProxy;
@@ -28,11 +29,12 @@
import com.azure.storage.blob.models.SourceModifiedAccessConditions;
import com.azure.storage.blob.models.StorageErrorException;
import io.netty.buffer.ByteBuf;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
import java.net.URL;
import java.time.OffsetDateTime;
import java.util.Map;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -64,6 +66,7 @@ public AppendBlobsImpl(AzureBlobStorageImpl client) {
* the proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Blobs AppendBlob")
private interface AppendBlobsService {
@PUT("{containerName}/{blob}")
@ExpectedResponses({201})
diff --git a/storage/client/src/main/java/com/azure/storage/blob/implementation/BlobsImpl.java b/storage/client/src/main/java/com/azure/storage/blob/implementation/BlobsImpl.java
index 669fe17b311f2..44a1d90ab3b0c 100644
--- a/storage/client/src/main/java/com/azure/storage/blob/implementation/BlobsImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/blob/implementation/BlobsImpl.java
@@ -11,9 +11,10 @@
import com.azure.core.annotations.HeaderParam;
import com.azure.core.annotations.Host;
import com.azure.core.annotations.HostParam;
-import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.PUT;
+import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.DateTimeRfc1123;
import com.azure.core.implementation.RestProxy;
@@ -44,10 +45,11 @@
import com.azure.storage.blob.models.ModifiedAccessConditions;
import com.azure.storage.blob.models.SourceModifiedAccessConditions;
import com.azure.storage.blob.models.StorageErrorException;
+import reactor.core.publisher.Mono;
+
import java.net.URL;
import java.time.OffsetDateTime;
import java.util.Map;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -79,6 +81,7 @@ public BlobsImpl(AzureBlobStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Blobs")
private interface BlobsService {
@GET("{containerName}/{blob}")
@ExpectedResponses({200, 206, 304})
diff --git a/storage/client/src/main/java/com/azure/storage/blob/implementation/BlockBlobsImpl.java b/storage/client/src/main/java/com/azure/storage/blob/implementation/BlockBlobsImpl.java
index b3e5e26825e2e..cd9bfbb060805 100644
--- a/storage/client/src/main/java/com/azure/storage/blob/implementation/BlockBlobsImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/blob/implementation/BlockBlobsImpl.java
@@ -10,9 +10,10 @@
import com.azure.core.annotations.HeaderParam;
import com.azure.core.annotations.Host;
import com.azure.core.annotations.HostParam;
-import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.PUT;
+import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.DateTimeRfc1123;
import com.azure.core.implementation.RestProxy;
@@ -32,11 +33,12 @@
import com.azure.storage.blob.models.SourceModifiedAccessConditions;
import com.azure.storage.blob.models.StorageErrorException;
import io.netty.buffer.ByteBuf;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
import java.net.URL;
import java.time.OffsetDateTime;
import java.util.Map;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -68,6 +70,7 @@ public BlockBlobsImpl(AzureBlobStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Blobs BlockBlob")
private interface BlockBlobsService {
@PUT("{containerName}/{blob}")
@ExpectedResponses({201})
diff --git a/storage/client/src/main/java/com/azure/storage/blob/implementation/ContainersImpl.java b/storage/client/src/main/java/com/azure/storage/blob/implementation/ContainersImpl.java
index d99d732046135..780b3a356a619 100644
--- a/storage/client/src/main/java/com/azure/storage/blob/implementation/ContainersImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/blob/implementation/ContainersImpl.java
@@ -11,13 +11,15 @@
import com.azure.core.annotations.HeaderParam;
import com.azure.core.annotations.Host;
import com.azure.core.annotations.HostParam;
-import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.PUT;
+import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.CollectionFormat;
import com.azure.core.implementation.DateTimeRfc1123;
import com.azure.core.implementation.RestProxy;
+import com.azure.core.implementation.serializer.jackson.JacksonAdapter;
import com.azure.core.util.Context;
import com.azure.storage.blob.models.ContainersAcquireLeaseResponse;
import com.azure.storage.blob.models.ContainersBreakLeaseResponse;
@@ -39,10 +41,11 @@
import com.azure.storage.blob.models.PublicAccessType;
import com.azure.storage.blob.models.SignedIdentifier;
import com.azure.storage.blob.models.StorageErrorException;
+import reactor.core.publisher.Mono;
+
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -74,6 +77,7 @@ public ContainersImpl(AzureBlobStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Blobs Containers")
private interface ContainersService {
@PUT("{containerName}")
@ExpectedResponses({201})
@@ -692,7 +696,7 @@ public Mono listBlobFlatSegmentWithRestRe
public Mono listBlobFlatSegmentWithRestResponseAsync(String containerName, String prefix, String marker, Integer maxresults, List include, Integer timeout, String requestId, Context context) {
final String restype = "container";
final String comp = "list";
- String includeConverted = this.client.serializerAdapter().serializeList(include, CollectionFormat.CSV);
+ String includeConverted = JacksonAdapter.createDefaultSerializerAdapter().serializeList(include, CollectionFormat.CSV);
return service.listBlobFlatSegment(containerName, this.client.url(), prefix, marker, maxresults, includeConverted, timeout, this.client.version(), requestId, restype, comp, context);
}
@@ -735,7 +739,7 @@ public Mono listBlobHierarchySegment
public Mono listBlobHierarchySegmentWithRestResponseAsync(String containerName, String delimiter, String prefix, String marker, Integer maxresults, List include, Integer timeout, String requestId, Context context) {
final String restype = "container";
final String comp = "list";
- String includeConverted = this.client.serializerAdapter().serializeList(include, CollectionFormat.CSV);
+ String includeConverted = JacksonAdapter.createDefaultSerializerAdapter().serializeList(include, CollectionFormat.CSV);
return service.listBlobHierarchySegment(containerName, this.client.url(), prefix, delimiter, marker, maxresults, includeConverted, timeout, this.client.version(), requestId, restype, comp, context);
}
diff --git a/storage/client/src/main/java/com/azure/storage/blob/implementation/PageBlobsImpl.java b/storage/client/src/main/java/com/azure/storage/blob/implementation/PageBlobsImpl.java
index 5afe42104b948..9b552a71186bb 100644
--- a/storage/client/src/main/java/com/azure/storage/blob/implementation/PageBlobsImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/blob/implementation/PageBlobsImpl.java
@@ -10,9 +10,10 @@
import com.azure.core.annotations.HeaderParam;
import com.azure.core.annotations.Host;
import com.azure.core.annotations.HostParam;
-import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.PUT;
+import com.azure.core.annotations.PathParam;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.DateTimeRfc1123;
import com.azure.core.implementation.RestProxy;
@@ -36,11 +37,12 @@
import com.azure.storage.blob.models.SourceModifiedAccessConditions;
import com.azure.storage.blob.models.StorageErrorException;
import io.netty.buffer.ByteBuf;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
import java.net.URL;
import java.time.OffsetDateTime;
import java.util.Map;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -72,6 +74,7 @@ public PageBlobsImpl(AzureBlobStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Blobs PageBlobs")
private interface PageBlobsService {
@PUT("{containerName}/{blob}")
@ExpectedResponses({201})
diff --git a/storage/client/src/main/java/com/azure/storage/blob/implementation/ServicesImpl.java b/storage/client/src/main/java/com/azure/storage/blob/implementation/ServicesImpl.java
index 8ead7b5335cce..a31982006724c 100644
--- a/storage/client/src/main/java/com/azure/storage/blob/implementation/ServicesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/blob/implementation/ServicesImpl.java
@@ -13,6 +13,7 @@
import com.azure.core.annotations.POST;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.util.Context;
@@ -58,6 +59,7 @@ public ServicesImpl(AzureBlobStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Blobs Service")
private interface ServicesService {
@PUT("")
@ExpectedResponses({202})
diff --git a/storage/client/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java b/storage/client/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java
new file mode 100644
index 0000000000000..fe93273e3f64e
--- /dev/null
+++ b/storage/client/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.storage.common.credentials;
+
+import com.azure.core.implementation.util.ImplUtils;
+
+import java.util.HashMap;
+
+/**
+ * Holds a SAS token used for authenticating requests.
+ */
+public final class SASTokenCredential {
+ // Required SAS token pieces
+ private static final String SIGNED_VERSION = "sv";
+ private static final String SIGNED_SERVICES = "ss";
+ private static final String SIGNED_RESOURCE_TYPES = "srt";
+ private static final String SIGNED_PERMISSIONS = "sp";
+ private static final String SIGNED_EXPIRY = "se";
+ private static final String SIGNATURE = "sig";
+
+ // Optional SAS token pieces
+ private static final String SIGNED_START = "st";
+ private static final String SIGNED_PROTOCOL = "spr";
+ private static final String SIGNED_IP = "sip";
+
+ private final String sasToken;
+
+ /**
+ * Creates a SAS token credential from the passed SAS token.
+ * @param sasToken SAS token used to authenticate requests with the service.
+ */
+ public SASTokenCredential(String sasToken) {
+ this.sasToken = sasToken;
+ }
+
+ /**
+ * @return the SAS token
+ */
+ public String sasToken() {
+ return sasToken;
+ }
+
+ /**
+ * Creates a SAS token credential from the passed URL query string
+ * @param query URL query used to build the SAS token
+ * @return a SAS token credential if the query param contains all the necessary pieces
+ */
+ public static SASTokenCredential fromQuery(String query) {
+ if (ImplUtils.isNullOrEmpty(query)) {
+ return null;
+ }
+
+ HashMap queryParams = new HashMap<>();
+ for (String queryParam : query.split("&")) {
+ String key = queryParam.split("=", 2)[0];
+ queryParams.put(key, queryParam);
+ }
+
+ if (queryParams.size() < 6
+ || !queryParams.containsKey(SIGNED_VERSION)
+ || !queryParams.containsKey(SIGNED_SERVICES)
+ || !queryParams.containsKey(SIGNED_RESOURCE_TYPES)
+ || !queryParams.containsKey(SIGNED_PERMISSIONS)
+ || !queryParams.containsKey(SIGNED_EXPIRY)
+ || !queryParams.containsKey(SIGNATURE)) {
+ return null;
+ }
+
+ StringBuilder sasTokenBuilder = new StringBuilder(queryParams.get(SIGNED_VERSION))
+ .append("&").append(queryParams.get(SIGNED_SERVICES))
+ .append("&").append(queryParams.get(SIGNED_RESOURCE_TYPES))
+ .append("&").append(queryParams.get(SIGNED_PERMISSIONS));
+
+ // SIGNED_START is optional
+ if (queryParams.containsKey(SIGNED_START)) {
+ sasTokenBuilder.append("&").append(queryParams.get(SIGNED_START));
+ }
+
+ sasTokenBuilder.append("&").append(queryParams.get(SIGNED_EXPIRY));
+
+ // SIGNED_IP is optional
+ if (queryParams.containsKey(SIGNED_IP)) {
+ sasTokenBuilder.append("&").append(queryParams.get(SIGNED_IP));
+ }
+
+ // SIGNED_PROTOCOL is optional
+ if (queryParams.containsKey(SIGNED_PROTOCOL)) {
+ sasTokenBuilder.append("&").append(queryParams.get(SIGNED_PROTOCOL));
+ }
+
+ sasTokenBuilder.append("&").append(queryParams.get(SIGNATURE));
+
+ return new SASTokenCredential(sasTokenBuilder.toString());
+ }
+}
diff --git a/storage/client/src/main/java/com/azure/storage/common/credentials/SharedKeyCredential.java b/storage/client/src/main/java/com/azure/storage/common/credentials/SharedKeyCredential.java
new file mode 100644
index 0000000000000..31bf01f4d5cbb
--- /dev/null
+++ b/storage/client/src/main/java/com/azure/storage/common/credentials/SharedKeyCredential.java
@@ -0,0 +1,185 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.storage.common.credentials;
+
+import com.azure.core.implementation.util.ImplUtils;
+import io.netty.handler.codec.http.QueryStringDecoder;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * SharedKey credential policy that is put into a header to authorize requests.
+ */
+public final class SharedKeyCredential {
+ private static final String AUTHORIZATION_HEADER_FORMAT = "SharedKey %s:%s";
+
+ // Pieces of the connection string that are needed.
+ private static final String ACCOUNT_NAME = "AccountName".toLowerCase();
+ private static final String ACCOUNT_KEY = "AccountKey".toLowerCase();
+
+ private final String accountName;
+ private final byte[] accountKey;
+
+ /**
+ * Initializes a new instance of SharedKeyCredentials contains an account's name and its primary or secondary
+ * accountKey.
+ *
+ * @param accountName The account name associated with the request.
+ * @param accountKey The account access key used to authenticate the request.
+ */
+ public SharedKeyCredential(String accountName, String accountKey) {
+ this.accountName = accountName;
+ this.accountKey = Base64.getDecoder().decode(accountKey);
+ }
+
+ /**
+ * Creates a SharedKey credential from the passed connection string.
+ * @param connectionString Connection string used to build the SharedKey credential.
+ * @return a SharedKey credential if the connection string contains AccountName and AccountKey
+ * @throws IllegalArgumentException If {@code connectionString} doesn't have AccountName or AccountKey.
+ */
+ public static SharedKeyCredential fromConnectionString(String connectionString) {
+ HashMap connectionStringPieces = new HashMap<>();
+ for (String connectionStringPiece : connectionString.split(";")) {
+ String[] kvp = connectionStringPiece.split("=", 2);
+ connectionStringPieces.put(kvp[0].toLowerCase(), kvp[1]);
+ }
+
+ String accountName = connectionStringPieces.get(ACCOUNT_NAME);
+ String accountKey = connectionStringPieces.get(ACCOUNT_KEY);
+
+ if (ImplUtils.isNullOrEmpty(accountName) || ImplUtils.isNullOrEmpty(accountKey)) {
+ throw new IllegalArgumentException("Connection string must contain 'AccountName' and 'AccountKey'.");
+ }
+
+ return new SharedKeyCredential(accountName, accountKey);
+ }
+
+ /**
+ * Generates the SharedKey Authorization value from information in the request.
+ * @param requestURL URL of the request
+ * @param httpMethod HTTP method being used
+ * @param headers Headers on the request
+ * @return the SharedKey authorization value
+ */
+ public String generateAuthorizationHeader(URL requestURL, String httpMethod, Map headers) {
+ return computeHMACSHA256(buildStringToSign(requestURL, httpMethod, headers));
+ }
+
+ private String buildStringToSign(URL requestURL, String httpMethod, Map headers) {
+ String contentLength = headers.get("Content-Length");
+ contentLength = contentLength.equals("0") ? "" : contentLength;
+
+ // If the x-ms-header exists ignore the Date header
+ String dateHeader = (headers.containsKey("x-ms-date")) ? "" : headers.getOrDefault("Date", "");
+
+ return String.join("\n",
+ httpMethod,
+ headers.getOrDefault("Content-Encoding", ""),
+ headers.getOrDefault("Content-Language", ""),
+ contentLength,
+ headers.getOrDefault("Content-MD5", ""),
+ headers.getOrDefault("Content-Type", ""),
+ dateHeader,
+ headers.getOrDefault("If-Modified-Since", ""),
+ headers.getOrDefault("If-Match", ""),
+ headers.getOrDefault("If-None-Match", ""),
+ headers.getOrDefault("If-Unmodified-Since", ""),
+ headers.getOrDefault("Range", ""),
+ getAdditionalXmsHeaders(headers),
+ getCanonicalizedResource(requestURL));
+ }
+
+ private String getAdditionalXmsHeaders(Map headers) {
+ // Add only headers that begin with 'x-ms-'
+ final List xmsHeaderNameArray = headers.entrySet().stream()
+ .filter(entry -> entry.getKey().toLowerCase(Locale.ROOT).startsWith("x-ms-"))
+ .filter(entry -> entry.getValue() != null)
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+
+ if (xmsHeaderNameArray.isEmpty()) {
+ return "";
+ }
+
+ Collections.sort(xmsHeaderNameArray);
+
+ final StringBuilder canonicalizedHeaders = new StringBuilder();
+ for (final String key : xmsHeaderNameArray) {
+ if (canonicalizedHeaders.length() > 0) {
+ canonicalizedHeaders.append('\n');
+ }
+
+ canonicalizedHeaders.append(key)
+ .append(':')
+ .append(headers.get(key));
+ }
+
+ return canonicalizedHeaders.toString();
+ }
+
+ private String getCanonicalizedResource(URL requestURL) {
+
+ // Resource path
+ final StringBuilder canonicalizedResource = new StringBuilder("/");
+ canonicalizedResource.append(accountName);
+
+ // Note that AbsolutePath starts with a '/'.
+ if (requestURL.getPath().length() > 0) {
+ canonicalizedResource.append(requestURL.getPath());
+ } else {
+ canonicalizedResource.append('/');
+ }
+
+ // check for no query params and return
+ if (requestURL.getQuery() == null) {
+ return canonicalizedResource.toString();
+ }
+
+ // The URL object's query field doesn't include the '?'. The QueryStringDecoder expects it.
+ QueryStringDecoder queryDecoder = new QueryStringDecoder("?" + requestURL.getQuery());
+ Map> queryParams = queryDecoder.parameters();
+
+ ArrayList queryParamNames = new ArrayList<>(queryParams.keySet());
+ Collections.sort(queryParamNames);
+
+ for (String queryParamName : queryParamNames) {
+ final List queryParamValues = queryParams.get(queryParamName);
+ Collections.sort(queryParamValues);
+ String queryParamValuesStr = String.join(",", queryParamValues);
+ canonicalizedResource.append("\n")
+ .append(queryParamName.toLowerCase(Locale.ROOT))
+ .append(":")
+ .append(queryParamValuesStr);
+ }
+
+ // append to main string builder the join of completed params with new line
+ return canonicalizedResource.toString();
+ }
+
+ private String computeHMACSHA256(String stringToSign) {
+ try {
+ Mac hmacSha256 = Mac.getInstance("HmacSHA256");
+ hmacSha256.init(new SecretKeySpec(accountKey, "HmacSHA256"));
+ byte[] utf8Bytes = stringToSign.getBytes(StandardCharsets.UTF_8);
+ String signature = Base64.getEncoder().encodeToString(hmacSha256.doFinal(utf8Bytes));
+ return String.format(AUTHORIZATION_HEADER_FORMAT, accountName, signature);
+ } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
+ throw new Error(ex);
+ }
+ }
+}
diff --git a/storage/client/src/main/java/com/azure/storage/common/credentials/package-info.java b/storage/client/src/main/java/com/azure/storage/common/credentials/package-info.java
new file mode 100644
index 0000000000000..b03314b4cc0b0
--- /dev/null
+++ b/storage/client/src/main/java/com/azure/storage/common/credentials/package-info.java
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+/**
+ * This package contains credentials used by Azure Storage services.
+ */
+package com.azure.storage.common.credentials;
diff --git a/storage/client/src/main/java/com/azure/storage/common/policy/SASTokenCredentialPolicy.java b/storage/client/src/main/java/com/azure/storage/common/policy/SASTokenCredentialPolicy.java
new file mode 100644
index 0000000000000..05aa24c47b51d
--- /dev/null
+++ b/storage/client/src/main/java/com/azure/storage/common/policy/SASTokenCredentialPolicy.java
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.storage.common.policy;
+
+import com.azure.core.http.HttpPipelineCallContext;
+import com.azure.core.http.HttpPipelineNextPolicy;
+import com.azure.core.http.HttpResponse;
+import com.azure.core.http.policy.HttpPipelinePolicy;
+import com.azure.core.implementation.util.ImplUtils;
+import com.azure.storage.common.credentials.SASTokenCredential;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Policy that adds the SAS token to the request URL's query.
+ */
+public final class SASTokenCredentialPolicy implements HttpPipelinePolicy {
+ private final SASTokenCredential credential;
+
+ /**
+ * Creates a SAS token credential policy that appends the SAS token to the request URL's query.
+ * @param credential SAS token credential
+ */
+ public SASTokenCredentialPolicy(SASTokenCredential credential) {
+ this.credential = credential;
+ }
+
+ @Override
+ public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+ try {
+ URL requestURL = context.httpRequest().url();
+ String delimiter = !ImplUtils.isNullOrEmpty(requestURL.getQuery()) ? "&" : "?";
+
+ String newURL = requestURL.toString() + delimiter + credential.sasToken();
+ context.httpRequest().withUrl(new URL(newURL));
+ } catch (MalformedURLException ex) {
+ throw new IllegalStateException(ex);
+ }
+
+ return next.process();
+ }
+}
diff --git a/storage/client/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java b/storage/client/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java
new file mode 100644
index 0000000000000..e107c62d8f58b
--- /dev/null
+++ b/storage/client/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.storage.common.policy;
+
+import com.azure.core.http.HttpPipelineCallContext;
+import com.azure.core.http.HttpPipelineNextPolicy;
+import com.azure.core.http.HttpResponse;
+import com.azure.core.http.policy.HttpPipelinePolicy;
+import com.azure.storage.common.credentials.SharedKeyCredential;
+import reactor.core.publisher.Mono;
+
+/**
+ * Policy that adds the SharedKey into the request's Authorization header.
+ */
+public final class SharedKeyCredentialPolicy implements HttpPipelinePolicy {
+ private final SharedKeyCredential credential;
+
+ /**
+ * Creates a SharedKey pipeline policy that adds the SharedKey into the request's authorization header.
+ * @param credential the SharedKey credential used to create the policy.
+ */
+ public SharedKeyCredentialPolicy(SharedKeyCredential credential) {
+ this.credential = credential;
+ }
+
+ @Override
+ public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+ String authorizationValue = credential.generateAuthorizationHeader(context.httpRequest().url(),
+ context.httpRequest().httpMethod().toString(),
+ context.httpRequest().headers().toMap());
+ context.httpRequest().withHeader("Authorization", authorizationValue);
+ return next.process();
+ }
+}
diff --git a/storage/client/src/main/java/com/azure/storage/common/policy/package-info.java b/storage/client/src/main/java/com/azure/storage/common/policy/package-info.java
new file mode 100644
index 0000000000000..6f36065ea580b
--- /dev/null
+++ b/storage/client/src/main/java/com/azure/storage/common/policy/package-info.java
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+/**
+ * This package contains policies used by Azure Storage services.
+ */
+package com.azure.storage.common.policy;
diff --git a/storage/client/src/main/java/com/azure/storage/file/implementation/DirectorysImpl.java b/storage/client/src/main/java/com/azure/storage/file/implementation/DirectorysImpl.java
index 27cf4b5c3e4dc..4e3f44c781ef9 100644
--- a/storage/client/src/main/java/com/azure/storage/file/implementation/DirectorysImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/file/implementation/DirectorysImpl.java
@@ -12,6 +12,7 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.util.Context;
@@ -23,9 +24,10 @@
import com.azure.storage.file.models.DirectorysListHandlesResponse;
import com.azure.storage.file.models.DirectorysSetMetadataResponse;
import com.azure.storage.file.models.StorageErrorException;
-import java.util.Map;
import reactor.core.publisher.Mono;
+import java.util.Map;
+
/**
* An instance of this class provides access to all the operations defined in
* Directorys.
@@ -56,6 +58,7 @@ public DirectorysImpl(AzureFileStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Files Directory")
private interface DirectorysService {
@PUT("{shareName}/{directory}")
@ExpectedResponses({201})
diff --git a/storage/client/src/main/java/com/azure/storage/file/implementation/FilesImpl.java b/storage/client/src/main/java/com/azure/storage/file/implementation/FilesImpl.java
index 31716b6bb4663..ee44ffe4f9830 100644
--- a/storage/client/src/main/java/com/azure/storage/file/implementation/FilesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/file/implementation/FilesImpl.java
@@ -14,6 +14,7 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.implementation.util.Base64Util;
@@ -34,10 +35,11 @@
import com.azure.storage.file.models.FilesUploadRangeResponse;
import com.azure.storage.file.models.StorageErrorException;
import io.netty.buffer.ByteBuf;
-import java.util.Map;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import java.util.Map;
+
/**
* An instance of this class provides access to all the operations defined in
* Files.
@@ -68,6 +70,7 @@ public FilesImpl(AzureFileStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Files File")
private interface FilesService {
@PUT("{shareName}/{directory}/{fileName}")
@ExpectedResponses({201})
diff --git a/storage/client/src/main/java/com/azure/storage/file/implementation/ServicesImpl.java b/storage/client/src/main/java/com/azure/storage/file/implementation/ServicesImpl.java
index 016affed3fdb4..5fcf68a794327 100644
--- a/storage/client/src/main/java/com/azure/storage/file/implementation/ServicesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/file/implementation/ServicesImpl.java
@@ -12,9 +12,11 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.CollectionFormat;
import com.azure.core.implementation.RestProxy;
+import com.azure.core.implementation.serializer.jackson.JacksonAdapter;
import com.azure.core.util.Context;
import com.azure.storage.file.models.ListSharesIncludeType;
import com.azure.storage.file.models.ServicesGetPropertiesResponse;
@@ -22,9 +24,10 @@
import com.azure.storage.file.models.ServicesSetPropertiesResponse;
import com.azure.storage.file.models.StorageErrorException;
import com.azure.storage.file.models.StorageServiceProperties;
-import java.util.List;
import reactor.core.publisher.Mono;
+import java.util.List;
+
/**
* An instance of this class provides access to all the operations defined in
* Services.
@@ -55,6 +58,7 @@ public ServicesImpl(AzureFileStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Files Service")
private interface ServicesService {
@PUT("")
@ExpectedResponses({202})
@@ -161,7 +165,7 @@ public Mono listSharesSegmentWithRestResponse
*/
public Mono listSharesSegmentWithRestResponseAsync(String prefix, String marker, Integer maxresults, List include, Integer timeout, Context context) {
final String comp = "list";
- String includeConverted = this.client.serializerAdapter().serializeList(include, CollectionFormat.CSV);
+ String includeConverted = JacksonAdapter.createDefaultSerializerAdapter().serializeList(include, CollectionFormat.CSV);
return service.listSharesSegment(this.client.url(), prefix, marker, maxresults, includeConverted, timeout, this.client.version(), comp, context);
}
}
diff --git a/storage/client/src/main/java/com/azure/storage/file/implementation/SharesImpl.java b/storage/client/src/main/java/com/azure/storage/file/implementation/SharesImpl.java
index fc33a9617511a..bdc5fbadff999 100644
--- a/storage/client/src/main/java/com/azure/storage/file/implementation/SharesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/file/implementation/SharesImpl.java
@@ -13,6 +13,7 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.util.Context;
@@ -28,9 +29,10 @@
import com.azure.storage.file.models.SharesSetQuotaResponse;
import com.azure.storage.file.models.SignedIdentifier;
import com.azure.storage.file.models.StorageErrorException;
+import reactor.core.publisher.Mono;
+
import java.util.List;
import java.util.Map;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -62,6 +64,7 @@ public SharesImpl(AzureFileStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Files Shares")
private interface SharesService {
@PUT("{shareName}")
@ExpectedResponses({201})
diff --git a/storage/client/src/main/java/com/azure/storage/queue/implementation/MessageIdsImpl.java b/storage/client/src/main/java/com/azure/storage/queue/implementation/MessageIdsImpl.java
index 72d834ce35dbb..8a705fe7e4af7 100644
--- a/storage/client/src/main/java/com/azure/storage/queue/implementation/MessageIdsImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/queue/implementation/MessageIdsImpl.java
@@ -12,6 +12,7 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.util.Context;
@@ -51,6 +52,7 @@ public MessageIdsImpl(AzureQueueStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Queues MessageId")
private interface MessageIdsService {
@PUT("{queueName}/messages/{messageid}")
@ExpectedResponses({204})
diff --git a/storage/client/src/main/java/com/azure/storage/queue/implementation/MessagesImpl.java b/storage/client/src/main/java/com/azure/storage/queue/implementation/MessagesImpl.java
index a5ba80ae3d945..ee59aae16b63f 100644
--- a/storage/client/src/main/java/com/azure/storage/queue/implementation/MessagesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/queue/implementation/MessagesImpl.java
@@ -13,6 +13,7 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.POST;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.util.Context;
@@ -54,6 +55,7 @@ public MessagesImpl(AzureQueueStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Queues Messages")
private interface MessagesService {
@GET("{queueName}/messages")
@ExpectedResponses({200})
diff --git a/storage/client/src/main/java/com/azure/storage/queue/implementation/QueuesImpl.java b/storage/client/src/main/java/com/azure/storage/queue/implementation/QueuesImpl.java
index 6fcf781f76bd4..2eb2d4d1ec2e2 100644
--- a/storage/client/src/main/java/com/azure/storage/queue/implementation/QueuesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/queue/implementation/QueuesImpl.java
@@ -13,6 +13,7 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.RestProxy;
import com.azure.core.util.Context;
@@ -24,9 +25,10 @@
import com.azure.storage.queue.models.QueuesSetMetadataResponse;
import com.azure.storage.queue.models.SignedIdentifier;
import com.azure.storage.queue.models.StorageErrorException;
+import reactor.core.publisher.Mono;
+
import java.util.List;
import java.util.Map;
-import reactor.core.publisher.Mono;
/**
* An instance of this class provides access to all the operations defined in
@@ -58,6 +60,7 @@ public QueuesImpl(AzureQueueStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Queues Queue")
private interface QueuesService {
@PUT("{queueName}")
@ExpectedResponses({201, 204})
diff --git a/storage/client/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java b/storage/client/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java
index 32273183faa57..dc353d45f5243 100644
--- a/storage/client/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java
+++ b/storage/client/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java
@@ -12,9 +12,11 @@
import com.azure.core.annotations.HostParam;
import com.azure.core.annotations.PUT;
import com.azure.core.annotations.QueryParam;
+import com.azure.core.annotations.Service;
import com.azure.core.annotations.UnexpectedResponseExceptionType;
import com.azure.core.implementation.CollectionFormat;
import com.azure.core.implementation.RestProxy;
+import com.azure.core.implementation.serializer.jackson.JacksonAdapter;
import com.azure.core.util.Context;
import com.azure.storage.queue.models.ListQueuesIncludeType;
import com.azure.storage.queue.models.ServicesGetPropertiesResponse;
@@ -23,9 +25,10 @@
import com.azure.storage.queue.models.ServicesSetPropertiesResponse;
import com.azure.storage.queue.models.StorageErrorException;
import com.azure.storage.queue.models.StorageServiceProperties;
-import java.util.List;
import reactor.core.publisher.Mono;
+import java.util.List;
+
/**
* An instance of this class provides access to all the operations defined in
* Services.
@@ -56,6 +59,7 @@ public ServicesImpl(AzureQueueStorageImpl client) {
* proxy service to perform REST calls.
*/
@Host("{url}")
+ @Service("Storage Queues Services")
private interface ServicesService {
@PUT("")
@ExpectedResponses({202})
@@ -203,7 +207,7 @@ public Mono listQueuesSegmentWithRestResponse
*/
public Mono listQueuesSegmentWithRestResponseAsync(String prefix, String marker, Integer maxresults, List include, Integer timeout, String requestId, Context context) {
final String comp = "list";
- String includeConverted = this.client.serializerAdapter().serializeList(include, CollectionFormat.CSV);
+ String includeConverted = JacksonAdapter.createDefaultSerializerAdapter().serializeList(include, CollectionFormat.CSV);
return service.listQueuesSegment(this.client.url(), prefix, marker, maxresults, includeConverted, timeout, this.client.version(), requestId, comp, context);
}
}
diff --git a/storage/client/src/test/java/com/azure/storage/blob/BlobPocTests.java b/storage/client/src/test/java/com/azure/storage/blob/BlobPocTests.java
index 9aa8fb5e06696..00e888fbff336 100644
--- a/storage/client/src/test/java/com/azure/storage/blob/BlobPocTests.java
+++ b/storage/client/src/test/java/com/azure/storage/blob/BlobPocTests.java
@@ -14,7 +14,6 @@
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.commons.codec.binary.Base64;
-import org.junit.Test;
import reactor.core.publisher.Flux;
import java.net.InetSocketAddress;
@@ -25,7 +24,7 @@
public class BlobPocTests {
- @Test
+ //@Test
public void testCreateBlob() {
AzureBlobStorageImpl client = new AzureBlobStorageImpl(HttpPipeline.builder().httpClient(HttpClient.createDefault().proxy(() -> new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888))))/*,
new HttpPipelinePolicy() {