diff --git a/sdk/storage/CMakeLists.txt b/sdk/storage/CMakeLists.txt index f6be37855..eeb237401 100644 --- a/sdk/storage/CMakeLists.txt +++ b/sdk/storage/CMakeLists.txt @@ -28,6 +28,7 @@ set(AZURE_STORAGE_COMMON_HEADER inc/common/storage_uri_builder.hpp inc/common/storage_version.hpp inc/common/xml_wrapper.hpp + inc/common/account_sas_builder.hpp ) set(AZURE_STORAGE_COMMON_SOURCE @@ -39,6 +40,7 @@ set(AZURE_STORAGE_COMMON_SOURCE src/common/storage_error.cpp src/common/storage_uri_builder.cpp src/common/xml_wrapper.cpp + src/common/account_sas_builder.cpp ) add_library(azure-storage-common ${AZURE_STORAGE_COMMON_HEADER} ${AZURE_STORAGE_COMMON_SOURCE}) @@ -93,6 +95,7 @@ set (AZURE_STORAGE_BLOB_HEADER inc/blobs/append_blob_client.hpp inc/blobs/blob_options.hpp inc/blobs/blob_responses.hpp + inc/blobs/blob_sas_builder.hpp inc/blobs/protocol/blob_rest_client.hpp ) @@ -103,6 +106,7 @@ set (AZURE_STORAGE_BLOB_SOURCE src/blobs/block_blob_client.cpp src/blobs/page_blob_client.cpp src/blobs/append_blob_client.cpp + src/blobs/blob_sas_builder.cpp ) add_library(azure-storage-blob ${AZURE_STORAGE_BLOB_HEADER} ${AZURE_STORAGE_BLOB_SOURCE}) diff --git a/sdk/storage/inc/blobs/blob_sas_builder.hpp b/sdk/storage/inc/blobs/blob_sas_builder.hpp new file mode 100644 index 000000000..37f91f7f7 --- /dev/null +++ b/sdk/storage/inc/blobs/blob_sas_builder.hpp @@ -0,0 +1,313 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "blobs/protocol/blob_rest_client.hpp" +#include "common/account_sas_builder.hpp" +#include "nullable.hpp" + +namespace Azure { namespace Storage { namespace Blobs { + + /** + * @brief Specifies which resources are accessible via the shared access signature. + */ + enum class BlobSasResource + { + /** + * @brief Grants access to the content and metadata of any blob in the container, and to + * the list of blobs in the container. + */ + Container, + + /** + * @brief Grants access to the content and metadata of the blob. + */ + Blob, + + /** + * @brief Grants access to the content and metadata of the specific snapshot, but not + * the corresponding root blob. + */ + BlobSnapshot, + + /** + * @brief Grants access to the content and metadata of the specific version, but not the + * corresponding root blob. + */ + BlobVersion, + }; + + /** + * @brief The list of permissions that can be set for a blob container's access policy. + */ + enum class BlobContainerSasPermissions + { + /** + * @brief Indicates that Read is permitted. + */ + Read = 1, + + /** + * @brief Indicates that Write is permitted. + */ + Write = 2, + + /** + * @brief Indicates that Delete is permitted. + */ + Delete = 4, + + /** + * @brief Indicates that List is permitted. + */ + List = 8, + + /** + * @brief Indicates that Add is permitted. + */ + Add = 16, + + /** + * @brief Indicates that Create is permitted. + */ + Create = 32, + + /** + * @brief Indicates that reading and writing tags is permitted. + */ + Tags = 64, + + /** + * @brief Indicates that deleting previous blob version is permitted. + */ + DeleteVersion = 128, + + /** + * @beirf Indicates that all permissions are set. + */ + All = ~0, + }; + + inline BlobContainerSasPermissions operator|( + BlobContainerSasPermissions lhs, + BlobContainerSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast( + static_cast(lhs) | static_cast(rhs)); + } + + inline BlobContainerSasPermissions operator&( + BlobContainerSasPermissions lhs, + BlobContainerSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast( + static_cast(lhs) & static_cast(rhs)); + } + + /** + * @brief The list of permissions that can be set for a blob's access policy. + */ + enum class BlobSasPermissions + { + /** + * @brief Indicates that Read is permitted. + */ + Read = 1, + + /** + * @brief Indicates that Write is permitted. + */ + Write = 2, + + /** + * @brief Indicates that Delete is permitted. + */ + + Delete = 4, + + /** + * @brief Indicates that Add is permitted. + */ + Add = 8, + + /** + * @brief Indicates that Create is permitted. + */ + Create = 16, + + /** + * @brief Indicates that reading and writing tags is permitted. + */ + Tags = 32, + + /** + * @brief Indicates that deleting previous blob version is permitted. + */ + DeleteVersion = 64, + + /** + * @beirf Indicates that all permissions are set. + */ + All = ~0, + }; + + inline BlobSasPermissions operator|(BlobSasPermissions lhs, BlobSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline BlobSasPermissions operator&(BlobSasPermissions lhs, BlobSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /** + * @brief BlobSasBuilder is used to generate a Shared Access Signature (SAS) for an Azure + * Storage container or blob. + */ + struct BlobSasBuilder + { + /** + * @brief The storage service version to use to authenticate requests made with this + * shared access signature, and the service version to use when handling requests made with this + * shared access signature. + */ + std::string Version = c_APIVersion; + + /** + * @brief The optional signed protocol field specifies the protocol permitted for a + * request made with the SAS. + */ + SasProtocol Protocol; + + /** + * @brief Optionally specify the time at which the shared access signature becomes + * valid. + */ + std::string StartsOn; + + /** + * @brief The time at which the shared access signature becomes invalid. This field must + * be omitted if it has been specified in an associated stored access policy. + */ + std::string ExpiresOn; + + /** + * @brief Specifies an IP address or a range of IP addresses from which to accept + * requests. If the IP address from which the request originates does not match the IP address + * or address range specified on the SAS token, the request is not authenticated. When + * specifying a range of IP addresses, note that the range is inclusive. + */ + Azure::Core::Nullable IPRange; + + /** + * @brief An optional unique value up to 64 characters in length that correlates to an + * access policy specified for the container. + */ + std::string Identifier; + + /** + * @brief The name of the blob container being made accessible. + */ + std::string ContainerName; + + /** + * @brief The name of the blob being made accessible, or empty for a container SAS.. + */ + std::string BlobName; + + /** + * @brief The name of the blob snapshot being made accessible, or empty for a container + * SAS and blob SAS. + */ + std::string Snapshot; + + /** + * @brief The id of the blob version being made accessible, or empty for a container + * SAS, blob SAS and blob snapshot SAS. + */ + std::string BlobVersionId; + + /** + * @brief Specifies which resources are accessible via the shared access signature. + */ + BlobSasResource Resource; + + /** + * @brief Override the value returned for Cache-Control response header.. + */ + std::string CacheControl; + + /** + * @brief Override the value returned for Content-Disposition response header.. + */ + std::string ContentDisposition; + + /** + * @brief Override the value returned for Content-Encoding response header.. + */ + std::string ContentEncoding; + + /** + * @brief Override the value returned for Content-Language response header.. + */ + std::string ContentLanguage; + + /** + * @brief Override the value returned for Content-Type response header.. + */ + std::string ContentType; + + /** + * @brief Sets the permissions for the blob container SAS. + * + * @param + * permissions The allowed permissions. + */ + void SetPermissions(BlobContainerSasPermissions permissions); + + /** + * @brief Sets the permissions for the blob SAS. + * + * @param permissions The + * allowed permissions. + */ + void SetPermissions(BlobSasPermissions permissions); + + /** + * @brief Uses the SharedKeyCredential to sign this shared access signature, to produce + * the proper SAS query parameters for authentication requests. + * + * @param credential + * The storage account's shared key credential. + * @return The SAS query parameters used for + * authenticating requests. + */ + std::string ToSasQueryParameters(const SharedKeyCredential& credential); + + /** + * @brief Uses an account's user delegation key to sign this shared access signature, to + * produce the proper SAS query parameters for authentication requests. + * + * @param + * credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey. + * @param accountName The name of the storage account. + * @return The SAS query parameters + * used for authenticating requests. + */ + std::string ToSasQueryParameters( + const UserDelegationKey& userDelegationKey, + const std::string& accountName); + + private: + std::string Permissions; + }; + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp b/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp index ccd5730a4..aa55cb13a 100644 --- a/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp +++ b/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp @@ -28,6 +28,8 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Http::BodyStream, std::function>; + constexpr static const char* c_APIVersion = "2019-12-12"; + struct AbortCopyBlobInfo { }; // struct AbortCopyBlobInfo @@ -1006,7 +1008,7 @@ namespace Azure { namespace Storage { namespace Blobs { { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -1109,7 +1111,7 @@ namespace Azure { namespace Storage { namespace Blobs { request.AddHeader("Content-Length", std::to_string(body->Length())); request.AddQueryParameter("restype", "service"); request.AddQueryParameter("comp", "userdelegationkey"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -1608,7 +1610,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("restype", "container"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -1684,7 +1686,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, url); request.AddQueryParameter("restype", "container"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -1752,7 +1754,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, url); request.AddQueryParameter("restype", "container"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -1852,7 +1854,7 @@ namespace Azure { namespace Storage { namespace Blobs { request.AddHeader("Content-Length", "0"); request.AddQueryParameter("restype", "container"); request.AddQueryParameter("comp", "metadata"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -1931,7 +1933,7 @@ namespace Azure { namespace Storage { namespace Blobs { { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -2022,7 +2024,7 @@ namespace Azure { namespace Storage { namespace Blobs { { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -2726,7 +2728,7 @@ namespace Azure { namespace Storage { namespace Blobs { { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -2935,7 +2937,7 @@ namespace Azure { namespace Storage { namespace Blobs { { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, url); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3012,7 +3014,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3069,7 +3071,7 @@ namespace Azure { namespace Storage { namespace Blobs { { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, url); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3290,7 +3292,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "properties"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3416,7 +3418,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "metadata"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3518,7 +3520,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "tier"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3591,7 +3593,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3716,7 +3718,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3785,7 +3787,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "snapshot"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -3916,7 +3918,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, body.get()); request.AddHeader("Content-Length", std::to_string(body->Length())); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -4087,7 +4089,7 @@ namespace Azure { namespace Storage { namespace Blobs { request.AddHeader("Content-Length", std::to_string(body->Length())); request.AddQueryParameter("comp", "block"); request.AddQueryParameter("blockid", options.BlockId); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -4201,7 +4203,7 @@ namespace Azure { namespace Storage { namespace Blobs { request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "block"); request.AddQueryParameter("blockid", options.BlockId); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -4357,7 +4359,7 @@ namespace Azure { namespace Storage { namespace Blobs { = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, body.get()); request.AddHeader("Content-Length", std::to_string(body->Length())); request.AddQueryParameter("comp", "blocklist"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -4506,7 +4508,7 @@ namespace Azure { namespace Storage { namespace Blobs { = BlockListTypeOptionToString(options.ListType.GetValue()); request.AddQueryParameter("blocklisttype", block_list_type_option); } - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -4746,7 +4748,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -4919,7 +4921,7 @@ namespace Azure { namespace Storage { namespace Blobs { = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, body.get()); request.AddHeader("Content-Length", std::to_string(body->Length())); request.AddQueryParameter("comp", "page"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -5078,7 +5080,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "page"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -5236,7 +5238,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "page"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -5371,7 +5373,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "properties"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -5489,7 +5491,7 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddQueryParameter("prevsnapshot", options.PreviousSnapshot.GetValue()); } - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -5606,7 +5608,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "incrementalcopy"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -5858,7 +5860,7 @@ namespace Azure { namespace Storage { namespace Blobs { unused(body); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -6019,7 +6021,7 @@ namespace Azure { namespace Storage { namespace Blobs { = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, body.get()); request.AddHeader("Content-Length", std::to_string(body->Length())); request.AddQueryParameter("comp", "appendblock"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); @@ -6164,7 +6166,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "appendblock"); - request.AddHeader("x-ms-version", "2019-12-12"); + request.AddHeader("x-ms-version", c_APIVersion); if (options.Timeout.HasValue()) { request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); diff --git a/sdk/storage/inc/common/account_sas_builder.hpp b/sdk/storage/inc/common/account_sas_builder.hpp new file mode 100644 index 000000000..1c37e506a --- /dev/null +++ b/sdk/storage/inc/common/account_sas_builder.hpp @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "common/constants.hpp" +#include "common/storage_credential.hpp" +#include "nullable.hpp" + +namespace Azure { namespace Storage { + + /** + * @brief Defines the protocols permitted for Storage requests made with a shared access + * signature. + */ + enum class SasProtocol + { + /** + * @brief Only requests issued over HTTPS or HTTP will be permitted. + */ + HttpsAndHtttp, + + /** + * @brief Only requests issued over HTTPS will be permitted. + */ + HttpsOnly, + }; + + /** + * @brief Specifies the resource types accessible from an account level shared access + * signature. + */ + enum class AccountSasResource + { + /** + * @brief Indicates whether service-level APIs are accessible from this shared access + * signature. + */ + Service = 1, + + /** + * @brief Indicates whether blob container-level APIs are accessible from this shared + * access signature. + */ + Container = 2, + + /** + * @brief Indicates whether object-level APIs for blobs, queue messages, and files are + * accessible from this shared access signature. + */ + Object = 4, + + /** + * @brief Indicates all service-level APIs are accessible from this shared access + * signature. + */ + All = ~0, + }; + + inline AccountSasResource operator|(AccountSasResource lhs, AccountSasResource rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline AccountSasResource operator&(AccountSasResource lhs, AccountSasResource rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /** + * @brief Specifies the services accessible from an account level shared access signature. + */ + enum class AccountSasServices + { + /** + * @brief Indicates whether Azure Blob Storage resources are accessible from the shared + * access signature. + */ + Blobs = 1, + + /** + * @brief Indicates whether Azure Queue Storage resources are accessible from the shared + * access signature. + */ + Queue = 2, + + /** + * @brief Indicates whether Azure File Storage resources are accessible from the shared + * access signature. + */ + Files = 4, + + /** + * @brief Indicates all services are accessible from the shared + * access signature. + */ + All = ~0, + }; + + inline AccountSasServices operator|(AccountSasServices lhs, AccountSasServices rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline AccountSasServices operator&(AccountSasServices lhs, AccountSasServices rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /** + * @brief The list of permissions that can be set for an account's access policy. + */ + enum class AccountSasPermissions + { + /** + * @brief Indicates that Read is permitted. + */ + Read = 1, + + /** + * @brief Indicates that Write is permitted. + */ + Write = 2, + + /** + * @brief Indicates that Delete is permitted. + */ + Delete = 4, + + /** + * @brief Indicates that deleting previous blob version is permitted. + */ + DeleteVersion = 8, + + /** + * @brief Indicates that List is permitted. + */ + List = 16, + + /** + * @brief Indicates that Add is permitted. + */ + Add = 32, + + /** + * @brief Indicates that Create is permitted. + */ + Create = 64, + + /** + * @brief Indicates that Update is permitted. + */ + Update = 128, + + /** + * @brief Indicates that Process is permitted. + */ + Process = 256, + + /** + * @brief Indicates that reading and writing tags is permitted. + */ + Tags = 512, + + /** + * @brief Indicates that filtering by tags is permitted. + */ + Filter = 1024, + + /** + * @brief Indicates that all permissions are set. + */ + All = ~0, + }; + + inline AccountSasPermissions operator|(AccountSasPermissions lhs, AccountSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline AccountSasPermissions operator&(AccountSasPermissions lhs, AccountSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /** + * @brief AccountSasBuilder is used to generate an account level Shared Access Signature + * (SAS) for Azure Storage services. + */ + struct AccountSasBuilder + { + /** + * @brief The storage service version to use to authenticate requests made with this + * shared access signature, and the service version to use when handling requests made with this + * shared access signature. + */ + std::string Version = Details::c_defaultSasVersion; + + /** + * @brief The optional signed protocol field specifies the protocol permitted for a + * request made with the SAS. + */ + SasProtocol Protocol = SasProtocol::HttpsOnly; + + /** + * @brief Optionally specify the time at which the shared access signature becomes + * valid. + */ + Azure::Core::Nullable StartsOn; + + /** + * @brief The time at which the shared access signature becomes invalid. This field must + * be omitted if it has been specified in an associated stored access policy. + */ + std::string ExpiresOn; + + /** + * @brief Specifies an IP address or a range of IP addresses from which to accept + * requests. If the IP address from which the request originates does not match the IP address + * or address range specified on the SAS token, the request is not authenticated. When + * specifying a range of IP addresses, note that the range is inclusive. + */ + Azure::Core::Nullable IPRange; + + /** + * @brief The services associated with the shared access signature. The user is + * restricted to operations with the specified services. + */ + AccountSasServices Services; + + /** + * The resource types associated with the shared access signature. The user is + * restricted to operations on the specified resources. + */ + AccountSasResource ResourceTypes; + + /** + * @brief Sets the permissions for an account SAS. + * + * @param permissions The + * allowed permissions. + */ + void SetPermissions(AccountSasPermissions permissions); + + /** + * @brief Uses the SharedKeyCredential to sign this shared access signature, to produce + * the proper SAS query parameters for authentication requests. + * + * @param credential + * The storage account's shared key credential. + * @return The SAS query parameters used for + * authenticating requests. + */ + std::string ToSasQueryParameters(const SharedKeyCredential& credential); + + private: + std::string Permissions; + }; + +}} // namespace Azure::Storage diff --git a/sdk/storage/inc/common/constants.hpp b/sdk/storage/inc/common/constants.hpp index ba760ab69..648f11a66 100644 --- a/sdk/storage/inc/common/constants.hpp +++ b/sdk/storage/inc/common/constants.hpp @@ -17,4 +17,5 @@ namespace Azure { namespace Storage { namespace Details { constexpr static const char* c_HttpHeaderRequestId = "x-ms-request-id"; constexpr static const char* c_HttpHeaderClientRequestId = "x-ms-client-request-id"; constexpr static const char* c_HttpHeaderContentType = "content-type"; + constexpr static const char* c_defaultSasVersion = "2019-12-12"; }}} // namespace Azure::Storage::Details diff --git a/sdk/storage/inc/common/storage_credential.hpp b/sdk/storage/inc/common/storage_credential.hpp index a29393b71..5c6f56871 100644 --- a/sdk/storage/inc/common/storage_credential.hpp +++ b/sdk/storage/inc/common/storage_credential.hpp @@ -11,6 +11,12 @@ #include namespace Azure { namespace Storage { + + struct AccountSasBuilder; + namespace Blobs { + struct BlobSasBuilder; + } + struct SharedKeyCredential { explicit SharedKeyCredential(std::string accountName, std::string accountKey) @@ -24,17 +30,19 @@ namespace Azure { namespace Storage { m_accountKey = std::move(accountKey); } - std::string AccountName; + const std::string AccountName; private: friend class SharedKeyPolicy; - std::string GetAccountKey() + friend struct Blobs::BlobSasBuilder; + friend struct AccountSasBuilder; + std::string GetAccountKey() const { std::lock_guard guard(m_mutex); return m_accountKey; } - std::mutex m_mutex; + mutable std::mutex m_mutex; std::string m_accountKey; }; diff --git a/sdk/storage/src/blobs/blob_sas_builder.cpp b/sdk/storage/src/blobs/blob_sas_builder.cpp new file mode 100644 index 000000000..cbdf26a88 --- /dev/null +++ b/sdk/storage/src/blobs/blob_sas_builder.cpp @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "blobs/blob_sas_builder.hpp" +#include "common/crypt.hpp" +#include "common/storage_uri_builder.hpp" + +namespace Azure { namespace Storage { namespace Blobs { + + void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions) + { + Permissions.clear(); + // The order matters + if ((permissions & BlobContainerSasPermissions::Read) == BlobContainerSasPermissions::Read) + { + Permissions += "r"; + } + if ((permissions & BlobContainerSasPermissions::Add) == BlobContainerSasPermissions::Add) + { + Permissions += "a"; + } + if ((permissions & BlobContainerSasPermissions::Create) == BlobContainerSasPermissions::Create) + { + Permissions += "c"; + } + if ((permissions & BlobContainerSasPermissions::Write) == BlobContainerSasPermissions::Write) + { + Permissions += "w"; + } + if ((permissions & BlobContainerSasPermissions::Delete) == BlobContainerSasPermissions::Delete) + { + Permissions += "d"; + } + if ((permissions & BlobContainerSasPermissions::DeleteVersion) + == BlobContainerSasPermissions::DeleteVersion) + { + Permissions += "x"; + } + if ((permissions & BlobContainerSasPermissions::List) == BlobContainerSasPermissions::List) + { + Permissions += "l"; + } + if ((permissions & BlobContainerSasPermissions::Tags) == BlobContainerSasPermissions::Tags) + { + Permissions += "t"; + } + } + + void BlobSasBuilder::SetPermissions(BlobSasPermissions permissions) + { + Permissions.clear(); + // The order matters + if ((permissions & BlobSasPermissions::Read) == BlobSasPermissions::Read) + { + Permissions += "r"; + } + if ((permissions & BlobSasPermissions::Add) == BlobSasPermissions::Add) + { + Permissions += "a"; + } + if ((permissions & BlobSasPermissions::Create) == BlobSasPermissions::Create) + { + Permissions += "c"; + } + if ((permissions & BlobSasPermissions::Write) == BlobSasPermissions::Write) + { + Permissions += "w"; + } + if ((permissions & BlobSasPermissions::Delete) == BlobSasPermissions::Delete) + { + Permissions += "d"; + } + if ((permissions & BlobSasPermissions::DeleteVersion) == BlobSasPermissions::DeleteVersion) + { + Permissions += "x"; + } + if ((permissions & BlobSasPermissions::Tags) == BlobSasPermissions::Tags) + { + Permissions += "t"; + } + } + + std::string BlobSasBuilder::ToSasQueryParameters(const SharedKeyCredential& credential) + { + std::string canonicalName = "/blob/" + credential.AccountName + "/" + ContainerName; + if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot) + { + canonicalName += "/" + BlobName; + } + std::string protocol; + if (Protocol == SasProtocol::HttpsAndHtttp) + { + protocol = "https,http"; + } + else + { + protocol = "https"; + } + std::string resource; + if (Resource == BlobSasResource::Container) + { + resource = "c"; + } + else if (Resource == BlobSasResource::Blob) + { + resource = "b"; + } + else if (Resource == BlobSasResource::BlobSnapshot) + { + resource = "bs"; + } + else if (Resource == BlobSasResource::BlobVersion) + { + resource = "bv"; + } + std::string stringToSign = Permissions + "\n" + StartsOn + "\n" + ExpiresOn + "\n" + + canonicalName + "\n" + Identifier + "\n" + (IPRange.HasValue() ? IPRange.GetValue() : "") + + "\n" + protocol + "\n" + Version + "\n" + resource + "\n" + Snapshot + "\n" + CacheControl + + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + + ContentType; + + std::string signature + = Base64Encode(HMAC_SHA256(stringToSign, Base64Decode(credential.GetAccountKey()))); + + UriBuilder builder; + builder.AppendQuery("sv", Version); + builder.AppendQuery("spr", protocol); + builder.AppendQuery("st", StartsOn); + builder.AppendQuery("se", ExpiresOn); + if (IPRange.HasValue()) + { + builder.AppendQuery("sip", IPRange.GetValue()); + } + if (!Identifier.empty()) + { + builder.AppendQuery("si", Identifier); + } + builder.AppendQuery("sr", resource); + builder.AppendQuery("sp", Permissions); + builder.AppendQuery("sig", signature, true); + if (!CacheControl.empty()) + { + builder.AppendQuery("rscc", CacheControl); + } + if (!ContentDisposition.empty()) + { + builder.AppendQuery("rscd", ContentDisposition); + } + if (!ContentEncoding.empty()) + { + builder.AppendQuery("rsce", ContentEncoding); + } + if (!ContentLanguage.empty()) + { + builder.AppendQuery("rscl", ContentLanguage); + } + if (!ContentType.empty()) + { + builder.AppendQuery("rsct", ContentType); + } + + return builder.ToString(); + } + + std::string BlobSasBuilder::ToSasQueryParameters( + const UserDelegationKey& userDelegationKey, + const std::string& accountName) + { + unused(userDelegationKey, accountName); + return std::string(); + } + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/common/account_sas_builder.cpp b/sdk/storage/src/common/account_sas_builder.cpp new file mode 100644 index 000000000..918a7bce8 --- /dev/null +++ b/sdk/storage/src/common/account_sas_builder.cpp @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "common/account_sas_builder.hpp" +#include "common/crypt.hpp" +#include "common/storage_uri_builder.hpp" + +namespace Azure { namespace Storage { + + void AccountSasBuilder::SetPermissions(AccountSasPermissions permissions) + { + Permissions.clear(); + if ((permissions & AccountSasPermissions::Read) == AccountSasPermissions::Read) + { + Permissions += "r"; + } + if ((permissions & AccountSasPermissions::Write) == AccountSasPermissions::Write) + { + Permissions += "w"; + } + if ((permissions & AccountSasPermissions::Delete) == AccountSasPermissions::Delete) + { + Permissions += "d"; + } + if ((permissions & AccountSasPermissions::DeleteVersion) + == AccountSasPermissions::DeleteVersion) + { + Permissions += "x"; + } + if ((permissions & AccountSasPermissions::List) == AccountSasPermissions::List) + { + Permissions += "l"; + } + if ((permissions & AccountSasPermissions::Add) == AccountSasPermissions::Add) + { + Permissions += "a"; + } + if ((permissions & AccountSasPermissions::Create) == AccountSasPermissions::Create) + { + Permissions += "c"; + } + if ((permissions & AccountSasPermissions::Update) == AccountSasPermissions::Update) + { + Permissions += "u"; + } + if ((permissions & AccountSasPermissions::Process) == AccountSasPermissions::Process) + { + Permissions += "p"; + } + if ((permissions & AccountSasPermissions::Tags) == AccountSasPermissions::Tags) + { + Permissions += "t"; + } + if ((permissions & AccountSasPermissions::Filter) == AccountSasPermissions::Filter) + { + Permissions += "f"; + } + } + + std::string AccountSasBuilder::ToSasQueryParameters(const SharedKeyCredential& credential) + { + std::string protocol; + if (Protocol == SasProtocol::HttpsAndHtttp) + { + protocol = "https,http"; + } + else + { + protocol = "https"; + } + std::string services; + if ((Services & AccountSasServices::Blobs) == AccountSasServices::Blobs) + { + services += "b"; + } + if ((Services & AccountSasServices::Queue) == AccountSasServices::Queue) + { + services += "q"; + } + if ((Services & AccountSasServices::Files) == AccountSasServices::Files) + { + services += "f"; + } + + std::string resourceTypes; + if ((ResourceTypes & AccountSasResource::Service) == AccountSasResource::Service) + { + resourceTypes += "s"; + } + if ((ResourceTypes & AccountSasResource::Container) == AccountSasResource::Container) + { + resourceTypes += "c"; + } + if ((ResourceTypes & AccountSasResource::Object) == AccountSasResource::Object) + { + resourceTypes += "o"; + } + + std::string stringToSign = credential.AccountName + "\n" + Permissions + "\n" + services + "\n" + + resourceTypes + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "") + "\n" + ExpiresOn + + "\n" + (IPRange.HasValue() ? IPRange.GetValue() : "") + "\n" + protocol + "\n" + Version + + "\n"; + + std::string signature + = Base64Encode(HMAC_SHA256(stringToSign, Base64Decode(credential.GetAccountKey()))); + + UriBuilder builder; + builder.AppendQuery("sv", Version); + builder.AppendQuery("ss", services); + builder.AppendQuery("srt", resourceTypes); + builder.AppendQuery("sp", Permissions); + if (StartsOn.HasValue()) + { + builder.AppendQuery("st", StartsOn.GetValue()); + } + builder.AppendQuery("se", ExpiresOn); + if (IPRange.HasValue()) + { + builder.AppendQuery("sip", IPRange.GetValue()); + } + builder.AppendQuery("spr", protocol); + builder.AppendQuery("sig", signature, true); + + return builder.ToString(); + } + +}} // namespace Azure::Storage diff --git a/sdk/storage/src/common/storage_uri_builder.cpp b/sdk/storage/src/common/storage_uri_builder.cpp index 3487f243f..bcd3fd86f 100644 --- a/sdk/storage/src/common/storage_uri_builder.cpp +++ b/sdk/storage/src/common/storage_uri_builder.cpp @@ -190,7 +190,6 @@ namespace Azure { namespace Storage { std::string full_url; if (!m_scheme.empty()) { - full_url += m_scheme + "://"; } full_url += m_host; diff --git a/sdk/storage/test/CMakeLists.txt b/sdk/storage/test/CMakeLists.txt index 56e1b5ffb..181a1f445 100644 --- a/sdk/storage/test/CMakeLists.txt +++ b/sdk/storage/test/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable ( blobs/append_blob_client_test.cpp blobs/page_blob_client_test.hpp blobs/page_blob_client_test.cpp + blobs/blob_sas_test.cpp blobs/performance_benchmark.cpp blobs/large_scale_test.cpp datalake/service_client_test.hpp diff --git a/sdk/storage/test/blobs/blob_sas_test.cpp b/sdk/storage/test/blobs/blob_sas_test.cpp new file mode 100644 index 000000000..a05c99461 --- /dev/null +++ b/sdk/storage/test/blobs/blob_sas_test.cpp @@ -0,0 +1,406 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "blob_container_client_test.hpp" +#include "blobs/blob_sas_builder.hpp" + +namespace Azure { namespace Storage { namespace Test { + + TEST_F(BlobContainerClientTest, BlobSasTest) + { + AccountSasBuilder accountSasBuilder; + accountSasBuilder.Protocol = SasProtocol::HttpsAndHtttp; + accountSasBuilder.StartsOn + = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + accountSasBuilder.ExpiresOn + = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(60)); + accountSasBuilder.Services = AccountSasServices::Blobs; + accountSasBuilder.ResourceTypes = AccountSasResource::Object | AccountSasResource::Container; + + std::string blobName = RandomString(); + Blobs::BlobSasBuilder blobSasBuilder; + blobSasBuilder.Protocol = SasProtocol::HttpsAndHtttp; + blobSasBuilder.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + blobSasBuilder.ExpiresOn + = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(60)); + blobSasBuilder.ContainerName = m_containerName; + blobSasBuilder.BlobName = blobName; + blobSasBuilder.Resource = Blobs::BlobSasResource::Blob; + + Blobs::BlobSasBuilder containerSasBuilder = blobSasBuilder; + containerSasBuilder.BlobName.clear(); + containerSasBuilder.Resource = Blobs::BlobSasResource::Container; + + auto keyCredential + = Details::ParseConnectionString(StandardStorageConnectionString()).KeyCredential; + auto blobServiceClient0 + = Blobs::BlobServiceClient::CreateFromConnectionString(StandardStorageConnectionString()); + auto blobContainerClient0 = blobServiceClient0.GetBlobContainerClient(m_containerName); + auto blobClient0 = blobContainerClient0.GetAppendBlobClient(blobName); + + auto serviceUri = blobServiceClient0.GetUri(); + auto containerUri = blobContainerClient0.GetUri(); + auto blobUri = blobClient0.GetUri(); + + auto verify_blob_read = [&](const std::string& sas) { + EXPECT_NO_THROW(blobClient0.Create()); + auto blobClient = Blobs::AppendBlobClient(blobUri + sas); + auto downloadedContent = blobClient.Download(); + EXPECT_TRUE(ReadBodyStream(downloadedContent->BodyStream).empty()); + }; + + auto verify_blob_write = [&](const std::string& sas) { + auto blobClient = Blobs::AppendBlobClient(blobUri + sas); + EXPECT_NO_THROW(blobClient.Create()); + }; + + auto verify_blob_delete = [&](const std::string& sas) { + blobClient0.Create(); + auto blobClient = Blobs::AppendBlobClient(blobUri + sas); + EXPECT_NO_THROW(blobClient.Delete()); + }; + + auto verify_blob_add = [&](const std::string& sas) { + blobClient0.Create(); + std::string content = "Hello world"; + auto blockContent = Azure::Core::Http::MemoryBodyStream( + reinterpret_cast(content.data()), content.size()); + auto blobClient = Blobs::AppendBlobClient(blobUri + sas); + EXPECT_NO_THROW(blobClient.AppendBlock(&blockContent)); + }; + + auto verify_blob_list = [&](const std::string& sas) { + auto blobContainerClient = Blobs::BlobContainerClient(containerUri + sas); + EXPECT_NO_THROW(blobContainerClient.ListBlobsFlat()); + }; + + auto verify_blob_create = [&](const std::string& sas) { + try + { + blobClient0.Delete(); + } + catch (StorageError&) + { + } + auto blobClient = Blobs::AppendBlobClient(blobUri + sas); + blobClient.Create(); + blobClient.CreateSnapshot(); + Blobs::DeleteBlobOptions options; + options.DeleteSnapshots = Blobs::DeleteSnapshotsOption::IncludeSnapshots; + blobClient0.Delete(options); + }; + + auto verify_blob_tags = [&](const std::string& sas) { + unused(sas); + // TODO: Add test for blob tags + }; + + auto verify_blob_filter = [&](const std::string& sas) { + unused(sas); + // TODO: Add test for blob tags + }; + + auto verify_blob_delete_version = [&](const std::string& sas) { + unused(sas); + // TODO: Add test for versions + }; + + for (auto permissions : { + AccountSasPermissions::All, + AccountSasPermissions::Read, + AccountSasPermissions::Write, + AccountSasPermissions::Delete, + AccountSasPermissions::DeleteVersion, + AccountSasPermissions::List, + AccountSasPermissions::Add, + AccountSasPermissions::Create, + AccountSasPermissions::Tags, + AccountSasPermissions::Filter, + }) + { + accountSasBuilder.SetPermissions(permissions); + auto sasToken = accountSasBuilder.ToSasQueryParameters(*keyCredential); + + if ((permissions & AccountSasPermissions::Read) == AccountSasPermissions::Read) + { + verify_blob_read(sasToken); + } + if ((permissions & AccountSasPermissions::Write) == AccountSasPermissions::Write) + { + verify_blob_write(sasToken); + } + if ((permissions & AccountSasPermissions::Delete) == AccountSasPermissions::Delete) + { + verify_blob_delete(sasToken); + } + if ((permissions & AccountSasPermissions::DeleteVersion) + == AccountSasPermissions::DeleteVersion) + { + verify_blob_delete_version(sasToken); + } + if ((permissions & AccountSasPermissions::List) == AccountSasPermissions::List) + { + verify_blob_list(sasToken); + } + if ((permissions & AccountSasPermissions::Add) == AccountSasPermissions::Add) + { + verify_blob_add(sasToken); + } + if ((permissions & AccountSasPermissions::Create) == AccountSasPermissions::Create) + { + verify_blob_create(sasToken); + } + if ((permissions & AccountSasPermissions::Tags) == AccountSasPermissions::Tags) + { + verify_blob_tags(sasToken); + } + if ((permissions & AccountSasPermissions::Filter) == AccountSasPermissions::Filter) + { + verify_blob_filter(sasToken); + } + } + + for (auto permissions : + {Blobs::BlobSasPermissions::All, + Blobs::BlobSasPermissions::Read, + Blobs::BlobSasPermissions::Write, + Blobs::BlobSasPermissions::Delete, + Blobs::BlobSasPermissions::Add, + Blobs::BlobSasPermissions::Create, + Blobs::BlobSasPermissions::Tags, + Blobs::BlobSasPermissions::DeleteVersion}) + { + blobSasBuilder.SetPermissions(permissions); + auto sasToken = blobSasBuilder.ToSasQueryParameters(*keyCredential); + + if ((permissions & Blobs::BlobSasPermissions::Read) == Blobs::BlobSasPermissions::Read) + { + verify_blob_read(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::Write) == Blobs::BlobSasPermissions::Write) + { + verify_blob_write(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::Delete) == Blobs::BlobSasPermissions::Delete) + { + verify_blob_delete(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::Add) == Blobs::BlobSasPermissions::Add) + { + verify_blob_add(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::Create) == Blobs::BlobSasPermissions::Create) + { + verify_blob_create(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::Tags) == Blobs::BlobSasPermissions::Tags) + { + verify_blob_tags(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::DeleteVersion) + == Blobs::BlobSasPermissions::DeleteVersion) + { + verify_blob_delete_version(sasToken); + } + } + + // Expires + { + AccountSasBuilder builder2 = accountSasBuilder; + builder2.SetPermissions(AccountSasPermissions::All); + builder2.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1)); + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_THROW(verify_blob_create(sasToken), std::runtime_error); + } + + // Without start time + { + AccountSasBuilder builder2 = accountSasBuilder; + builder2.SetPermissions(AccountSasPermissions::All); + builder2.StartsOn.Reset(); + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_NO_THROW(verify_blob_create(sasToken)); + } + + // IP + { + AccountSasBuilder builder2 = accountSasBuilder; + builder2.SetPermissions(AccountSasPermissions::All); + builder2.IPRange = "1.1.1.1"; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_THROW(verify_blob_create(sasToken), std::runtime_error); + builder2.IPRange = "0.0.0.0-255.255.255.255"; + sasToken = builder2.ToSasQueryParameters(*keyCredential); + blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_NO_THROW(verify_blob_create(sasToken)); + } + + // Account SAS Service + { + AccountSasBuilder builder2 = accountSasBuilder; + builder2.SetPermissions(AccountSasPermissions::All); + builder2.Services = AccountSasServices::Files; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_THROW(verify_blob_create(sasToken), std::runtime_error); + + builder2.Services = AccountSasServices::All; + sasToken = builder2.ToSasQueryParameters(*keyCredential); + blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_NO_THROW(verify_blob_create(sasToken)); + } + + // Account SAS Resource Types + { + AccountSasBuilder builder2 = accountSasBuilder; + builder2.SetPermissions(AccountSasPermissions::All); + builder2.ResourceTypes = AccountSasResource::Service; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_THROW(verify_blob_create(sasToken), std::runtime_error); + + auto serviceClient = Blobs::BlobServiceClient(serviceUri + sasToken); + EXPECT_NO_THROW(serviceClient.ListBlobContainersSegment()); + } + + for (auto permissions : + {Blobs::BlobContainerSasPermissions::All, + Blobs::BlobContainerSasPermissions::Read, + Blobs::BlobContainerSasPermissions::Write, + Blobs::BlobContainerSasPermissions::Delete, + Blobs::BlobContainerSasPermissions::List, + Blobs::BlobContainerSasPermissions::Add, + Blobs::BlobContainerSasPermissions::Create, + Blobs::BlobContainerSasPermissions::Tags}) + { + containerSasBuilder.SetPermissions(permissions); + auto sasToken = containerSasBuilder.ToSasQueryParameters(*keyCredential); + if ((permissions & Blobs::BlobContainerSasPermissions::Read) + == Blobs::BlobContainerSasPermissions::Read) + { + verify_blob_read(sasToken); + } + if ((permissions & Blobs::BlobContainerSasPermissions::Write) + == Blobs::BlobContainerSasPermissions::Write) + { + verify_blob_write(sasToken); + } + if ((permissions & Blobs::BlobContainerSasPermissions::Delete) + == Blobs::BlobContainerSasPermissions::Delete) + { + verify_blob_delete(sasToken); + } + if ((permissions & Blobs::BlobContainerSasPermissions::List) + == Blobs::BlobContainerSasPermissions::List) + { + verify_blob_list(sasToken); + } + if ((permissions & Blobs::BlobContainerSasPermissions::Add) + == Blobs::BlobContainerSasPermissions::Add) + { + verify_blob_add(sasToken); + } + if ((permissions & Blobs::BlobContainerSasPermissions::Create) + == Blobs::BlobContainerSasPermissions::Create) + { + verify_blob_create(sasToken); + } + if ((permissions & Blobs::BlobContainerSasPermissions::Tags) + == Blobs::BlobContainerSasPermissions::Tags) + { + verify_blob_tags(sasToken); + } + } + + blobSasBuilder.SetPermissions(Blobs::BlobSasPermissions::All); + // Expires + { + Blobs::BlobSasBuilder builder2 = blobSasBuilder; + builder2.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1)); + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_THROW(verify_blob_create(sasToken), std::runtime_error); + } + + // IP + { + Blobs::BlobSasBuilder builder2 = blobSasBuilder; + builder2.IPRange = "0.0.0.0-0.0.0.1"; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_THROW(verify_blob_create(sasToken), std::runtime_error); + builder2.IPRange = "0.0.0.0-255.255.255.255"; + sasToken = builder2.ToSasQueryParameters(*keyCredential); + blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + EXPECT_NO_THROW(verify_blob_create(sasToken)); + } + + // response headers override + { + Blobs::BlobHttpHeaders headers; + headers.ContentType = "application/x-binary"; + headers.ContentLanguage = "en-US"; + headers.ContentDisposition = "attachment"; + headers.CacheControl = "no-cache"; + headers.ContentEncoding = "identify"; + + Blobs::BlobSasBuilder builder2 = blobSasBuilder; + builder2.ContentType = "application/x-binary"; + builder2.ContentLanguage = "en-US"; + builder2.ContentDisposition = "attachment"; + builder2.CacheControl = "no-cache"; + builder2.ContentEncoding = "identify"; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); + blobClient0.Create(); + auto p = blobClient.GetProperties(); + EXPECT_EQ(p->HttpHeaders.ContentType, headers.ContentType); + EXPECT_EQ(p->HttpHeaders.ContentLanguage, headers.ContentLanguage); + EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition); + EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl); + EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding); + } + + blobClient0.Create(); + Blobs::BlobSasBuilder BlobSnapshotSasBuilder = blobSasBuilder; + BlobSnapshotSasBuilder.Resource = Blobs::BlobSasResource::BlobSnapshot; + + std::string blobSnapshotUri; + + auto verify_blob_snapshot_read = [&](const std::string sas) { + auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUri + "&" + sas.substr(1)); + auto downloadedContent = blobSnapshotClient.Download(); + EXPECT_TRUE(ReadBodyStream(downloadedContent->BodyStream).empty()); + }; + + auto verify_blob_snapshot_delete = [&](const std::string sas) { + auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUri + "&" + sas.substr(1)); + EXPECT_NO_THROW(blobSnapshotClient.Delete()); + }; + + for (auto permissions : { + Blobs::BlobSasPermissions::Read | Blobs::BlobSasPermissions::Delete, + Blobs::BlobSasPermissions::Read, + Blobs::BlobSasPermissions::Delete, + }) + { + std::string snapshot = blobClient0.CreateSnapshot()->Snapshot; + BlobSnapshotSasBuilder.Snapshot = snapshot; + blobSnapshotUri = blobClient0.WithSnapshot(snapshot).GetUri(); + BlobSnapshotSasBuilder.SetPermissions(permissions); + auto sasToken = BlobSnapshotSasBuilder.ToSasQueryParameters(*keyCredential); + + if ((permissions & Blobs::BlobSasPermissions::Read) == Blobs::BlobSasPermissions::Read) + { + verify_blob_snapshot_read(sasToken); + } + if ((permissions & Blobs::BlobSasPermissions::Delete) == Blobs::BlobSasPermissions::Delete) + { + verify_blob_snapshot_delete(sasToken); + } + } + } + +}}} // namespace Azure::Storage::Test diff --git a/sdk/storage/test/test_base.cpp b/sdk/storage/test/test_base.cpp index a41e6f648..69cee1ace 100644 --- a/sdk/storage/test/test_base.cpp +++ b/sdk/storage/test/test_base.cpp @@ -11,8 +11,11 @@ #include #include #include +#include +#include #include #include +#include #include namespace Azure { namespace Storage { namespace Test { @@ -214,4 +217,33 @@ namespace Azure { namespace Storage { namespace Test { return result; } + std::string ToISO8601(const std::chrono::system_clock::time_point& time_point) + { + std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(time_point); + struct tm ct; +#ifdef _WIN32 + gmtime_s(&ct, &epoch_seconds); +#else + gmtime_r(&epoch_seconds, &ct); +#endif + char buff[64]; + std::strftime(buff, sizeof(buff), "%Y-%m-%dT%H:%M:%SZ", &ct); + return std::string(buff); + } + + std::string ToRFC1123(const std::chrono::system_clock::time_point& time_point) + { + std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(time_point); + struct tm ct; +#ifdef _WIN32 + gmtime_s(&ct, &epoch_seconds); +#else + gmtime_r(&epoch_seconds, &ct); +#endif + std::stringstream ss; + ss.imbue(std::locale("C")); + ss << std::put_time(&ct, "%a, %d %b %Y %H:%M:%S GMT"); + return ss.str(); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/test/test_base.hpp b/sdk/storage/test/test_base.hpp index 25d4311b9..118a21042 100644 --- a/sdk/storage/test/test_base.hpp +++ b/sdk/storage/test/test_base.hpp @@ -9,6 +9,8 @@ #include "gtest/gtest.h" +#include + namespace Azure { namespace Storage { namespace Test { const std::string& StandardStorageConnectionString(); @@ -59,4 +61,7 @@ namespace Azure { namespace Storage { namespace Test { void DeleteFile(const std::string& filename); + std::string ToISO8601(const std::chrono::system_clock::time_point& time_point); + std::string ToRFC1123(const std::chrono::system_clock::time_point& time_point); + }}} // namespace Azure::Storage::Test