From 8867c7abcb63d0d9eecd93552639ca5837b57328 Mon Sep 17 00:00:00 2001 From: JinmingHu Date: Tue, 4 Aug 2020 09:53:34 +0800 Subject: [PATCH] [Storage Blob Service] Get/SetBlobContainerAccessPolicy (#381) * Get/SetBlobContainerAccessPolicy * Remove ResetPermissions() --- .../inc/blobs/blob_container_client.hpp | 26 ++ sdk/storage/inc/blobs/blob_options.hpp | 44 +++ sdk/storage/inc/blobs/blob_sas_builder.hpp | 7 +- .../inc/blobs/protocol/blob_rest_client.hpp | 305 ++++++++++++++++++ .../src/blobs/blob_container_client.cpp | 22 ++ sdk/storage/src/blobs/blob_sas_builder.cpp | 31 +- .../test/blobs/blob_container_client_test.cpp | 47 +++ sdk/storage/test/blobs/blob_sas_test.cpp | 25 +- sdk/storage/test/test_base.cpp | 25 +- sdk/storage/test/test_base.hpp | 4 +- 10 files changed, 517 insertions(+), 19 deletions(-) diff --git a/sdk/storage/inc/blobs/blob_container_client.hpp b/sdk/storage/inc/blobs/blob_container_client.hpp index 94cad2ddd..ca915c831 100644 --- a/sdk/storage/inc/blobs/blob_container_client.hpp +++ b/sdk/storage/inc/blobs/blob_container_client.hpp @@ -203,6 +203,32 @@ namespace Azure { namespace Storage { namespace Blobs { const std::string& delimiter, const ListBlobsOptions& options = ListBlobsOptions()) const; + /** + * @brief Gets the permissions for this container. The permissions indicate whether + * container data may be accessed publicly. + * + * @param options Optional parameters to + * execute this function. + * @return A BlobContainerAccessPolicy describing the container's + * access policy. + */ + Azure::Core::Response GetAccessPolicy( + const GetBlobContainerAccessPolicyOptions& options + = GetBlobContainerAccessPolicyOptions()) const; + + /** + * @brief Sets the permissions for the specified container. The permissions indicate + * whether blob container data may be accessed publicly. + * + * @param options Optional + * parameters to execute this function. + * @return A BlobContainerInfo describing the + * updated container. + */ + Azure::Core::Response SetAccessPolicy( + const SetBlobContainerAccessPolicyOptions& options + = SetBlobContainerAccessPolicyOptions()) const; + private: UriBuilder m_containerUrl; std::shared_ptr m_pipeline; diff --git a/sdk/storage/inc/blobs/blob_options.hpp b/sdk/storage/inc/blobs/blob_options.hpp index 295ebb923..5ca95dc4a 100644 --- a/sdk/storage/inc/blobs/blob_options.hpp +++ b/sdk/storage/inc/blobs/blob_options.hpp @@ -306,6 +306,50 @@ namespace Azure { namespace Storage { namespace Blobs { ListBlobsIncludeItem Include = ListBlobsIncludeItem::None; }; + /** + * @brief Optional parameters for BlobContainerClient::GetAccessPolicy. + */ + struct GetBlobContainerAccessPolicyOptions + { + /** + * @brief Context for cancelling long running operations. + */ + Azure::Core::Context Context; + + /** + * @brief Optional conditions that must be met to perform this operation. + */ + LeaseAccessConditions AccessConditions; + }; + + /** + * @brief Optional parameters for BlobContainerClient::SetAccessPolicy. + */ + struct SetBlobContainerAccessPolicyOptions + { + /** + * @brief Context for cancelling long running operations. + */ + Azure::Core::Context Context; + + /** + * @brief Specifies whether data in the container may be accessed publicly and the level + * of access. + */ + Azure::Core::Nullable AccessType; + + /** + * @brief Stored access policies that you can use to provide fine grained control over + * container permissions. + */ + std::vector SignedIdentifiers; + + /** + * @brief Optional conditions that must be met to perform this operation. + */ + ContainerAccessConditions AccessConditions; + }; + /** * @brief Blob client options used to initalize BlobClient. */ diff --git a/sdk/storage/inc/blobs/blob_sas_builder.hpp b/sdk/storage/inc/blobs/blob_sas_builder.hpp index 0f3bef386..766413b07 100644 --- a/sdk/storage/inc/blobs/blob_sas_builder.hpp +++ b/sdk/storage/inc/blobs/blob_sas_builder.hpp @@ -109,6 +109,8 @@ namespace Azure { namespace Storage { namespace Blobs { static_cast(lhs) & static_cast(rhs)); } + std::string BlobContainerSasPermissionsToString(BlobContainerSasPermissions permissions); + /** * @brief The list of permissions that can be set for a blob's access policy. */ @@ -271,7 +273,10 @@ namespace Azure { namespace Storage { namespace Blobs { * @param * permissions The allowed permissions. */ - void SetPermissions(BlobContainerSasPermissions permissions); + void SetPermissions(BlobContainerSasPermissions permissions) + { + Permissions = BlobContainerSasPermissionsToString(permissions); + } /** * @brief Sets the permissions for the blob SAS. diff --git a/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp b/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp index 9d251ef44..bca15f676 100644 --- a/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp +++ b/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp @@ -478,6 +478,14 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Nullable Days; }; // struct BlobRetentionPolicy + struct BlobSignedIdentifier + { + std::string Id; + std::string StartsOn; + std::string ExpiresOn; + std::string Permissions; + }; // struct BlobSignedIdentifier + struct BlobSnapshotInfo { std::string Snapshot; @@ -1081,6 +1089,14 @@ namespace Azure { namespace Storage { namespace Blobs { std::vector UncommittedBlocks; }; // struct BlobBlockListInfo + struct BlobContainerAccessPolicy + { + PublicAccessType AccessType; + std::string ETag; + std::string LastModified; + std::vector SignedIdentifiers; + }; // struct BlobContainerAccessPolicy + struct BlobContainerItem { std::string Name; @@ -3093,7 +3109,177 @@ namespace Azure { namespace Storage { namespace Blobs { std::move(response), std::move(pHttpResponse)); } + struct GetAccessPolicyOptions + { + Azure::Core::Nullable Timeout; + Azure::Core::Nullable LeaseId; + }; // struct GetAccessPolicyOptions + + static Azure::Core::Response GetAccessPolicy( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const GetAccessPolicyOptions& options) + { + unused(options); + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); + request.AddHeader("x-ms-version", c_APIVersion); + if (options.Timeout.HasValue()) + { + request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); + } + request.AddQueryParameter("restype", "container"); + request.AddQueryParameter("comp", "acl"); + auto pHttpResponse = pipeline.Send(context, request); + Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse; + BlobContainerAccessPolicy response; + auto http_status_code + = static_cast::type>( + httpResponse.GetStatusCode()); + if (!(http_status_code == 200)) + { + throw StorageError::CreateFromResponse(context, std::move(pHttpResponse)); + } + { + const auto& httpResponseBody = httpResponse.GetBody(); + XmlReader reader( + reinterpret_cast(httpResponseBody.data()), httpResponseBody.size()); + response = BlobContainerAccessPolicyFromXml(reader); + } + response.ETag = httpResponse.GetHeaders().at("etag"); + response.LastModified = httpResponse.GetHeaders().at("last-modified"); + response.AccessType + = PublicAccessTypeFromString(httpResponse.GetHeaders().at("x-ms-blob-public-access")); + return Azure::Core::Response( + std::move(response), std::move(pHttpResponse)); + } + + struct SetAccessPolicyOptions + { + Azure::Core::Nullable Timeout; + Azure::Core::Nullable AccessType; + Azure::Core::Nullable LeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + std::vector SignedIdentifiers; + }; // struct SetAccessPolicyOptions + + static Azure::Core::Response SetAccessPolicy( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const SetAccessPolicyOptions& options) + { + unused(options); + std::string xml_body; + { + XmlWriter writer; + SetAccessPolicyOptionsToXml(writer, options); + xml_body = writer.GetDocument(); + writer.Write(XmlNode{XmlNodeType::End}); + } + Azure::Core::Http::MemoryBodyStream xml_body_stream( + reinterpret_cast(xml_body.data()), xml_body.length()); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, &xml_body_stream); + request.AddHeader("Content-Length", std::to_string(xml_body_stream.Length())); + request.AddHeader("x-ms-version", c_APIVersion); + if (options.Timeout.HasValue()) + { + request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue())); + } + request.AddQueryParameter("restype", "container"); + request.AddQueryParameter("comp", "acl"); + if (options.AccessType.HasValue()) + { + request.AddHeader( + "x-ms-blob-public-access", PublicAccessTypeToString(options.AccessType.GetValue())); + } + if (options.LeaseId.HasValue()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId.GetValue()); + } + if (options.IfModifiedSince.HasValue()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue()); + } + if (options.IfUnmodifiedSince.HasValue()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue()); + } + auto pHttpResponse = pipeline.Send(context, request); + Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse; + BlobContainerInfo response; + auto http_status_code + = static_cast::type>( + httpResponse.GetStatusCode()); + if (!(http_status_code == 200)) + { + throw StorageError::CreateFromResponse(context, std::move(pHttpResponse)); + } + response.ETag = httpResponse.GetHeaders().at("etag"); + response.LastModified = httpResponse.GetHeaders().at("last-modified"); + return Azure::Core::Response( + std::move(response), std::move(pHttpResponse)); + } + private: + static BlobContainerAccessPolicy BlobContainerAccessPolicyFromXml(XmlReader& reader) + { + BlobContainerAccessPolicy ret; + enum class XmlTagName + { + k_SignedIdentifiers, + k_SignedIdentifier, + k_Unknown, + }; + std::vector path; + while (true) + { + auto node = reader.Read(); + if (node.Type == XmlNodeType::End) + { + break; + } + else if (node.Type == XmlNodeType::EndTag) + { + if (path.size() > 0) + { + path.pop_back(); + } + else + { + break; + } + } + else if (node.Type == XmlNodeType::StartTag) + { + if (std::strcmp(node.Name, "SignedIdentifiers") == 0) + { + path.emplace_back(XmlTagName::k_SignedIdentifiers); + } + else if (std::strcmp(node.Name, "SignedIdentifier") == 0) + { + path.emplace_back(XmlTagName::k_SignedIdentifier); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 2 && path[0] == XmlTagName::k_SignedIdentifiers + && path[1] == XmlTagName::k_SignedIdentifier) + { + ret.SignedIdentifiers.emplace_back(BlobSignedIdentifierFromXml(reader)); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + } + } + return ret; + } + static BlobsFlatSegment BlobsFlatSegmentFromXml(XmlReader& reader) { BlobsFlatSegment ret; @@ -3660,6 +3846,93 @@ namespace Azure { namespace Storage { namespace Blobs { return ret; } + static BlobSignedIdentifier BlobSignedIdentifierFromXml(XmlReader& reader) + { + BlobSignedIdentifier ret; + enum class XmlTagName + { + k_Id, + k_AccessPolicy, + k_Start, + k_Expiry, + k_Permission, + k_Unknown, + }; + std::vector path; + while (true) + { + auto node = reader.Read(); + if (node.Type == XmlNodeType::End) + { + break; + } + else if (node.Type == XmlNodeType::EndTag) + { + if (path.size() > 0) + { + path.pop_back(); + } + else + { + break; + } + } + else if (node.Type == XmlNodeType::StartTag) + { + if (std::strcmp(node.Name, "Id") == 0) + { + path.emplace_back(XmlTagName::k_Id); + } + else if (std::strcmp(node.Name, "AccessPolicy") == 0) + { + path.emplace_back(XmlTagName::k_AccessPolicy); + } + else if (std::strcmp(node.Name, "Start") == 0) + { + path.emplace_back(XmlTagName::k_Start); + } + else if (std::strcmp(node.Name, "Expiry") == 0) + { + path.emplace_back(XmlTagName::k_Expiry); + } + else if (std::strcmp(node.Name, "Permission") == 0) + { + path.emplace_back(XmlTagName::k_Permission); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 1 && path[0] == XmlTagName::k_Id) + { + ret.Id = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_AccessPolicy + && path[1] == XmlTagName::k_Start) + { + ret.StartsOn = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_AccessPolicy + && path[1] == XmlTagName::k_Expiry) + { + ret.ExpiresOn = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_AccessPolicy + && path[1] == XmlTagName::k_Permission) + { + ret.Permissions = node.Value; + } + } + } + return ret; + } + static std::map MetadataFromXml(XmlReader& reader) { std::map ret; @@ -3694,6 +3967,38 @@ namespace Azure { namespace Storage { namespace Blobs { return ret; } + static void SetAccessPolicyOptionsToXml( + XmlWriter& writer, + const SetAccessPolicyOptions& options) + { + writer.Write(XmlNode{XmlNodeType::StartTag, "SignedIdentifiers"}); + for (const auto& i : options.SignedIdentifiers) + { + BlobSignedIdentifierToXml(writer, i); + } + writer.Write(XmlNode{XmlNodeType::EndTag}); + } + + static void BlobSignedIdentifierToXml(XmlWriter& writer, const BlobSignedIdentifier& options) + { + writer.Write(XmlNode{XmlNodeType::StartTag, "SignedIdentifier"}); + writer.Write(XmlNode{XmlNodeType::StartTag, "Id"}); + writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.Id.data()}); + writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::StartTag, "AccessPolicy"}); + writer.Write(XmlNode{XmlNodeType::StartTag, "Start"}); + writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.StartsOn.data()}); + writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::StartTag, "Expiry"}); + writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.ExpiresOn.data()}); + writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::StartTag, "Permission"}); + writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.Permissions.data()}); + writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::EndTag}); + } + }; // class Container class Blob { diff --git a/sdk/storage/src/blobs/blob_container_client.cpp b/sdk/storage/src/blobs/blob_container_client.cpp index 99091f28e..9151d35e6 100644 --- a/sdk/storage/src/blobs/blob_container_client.cpp +++ b/sdk/storage/src/blobs/blob_container_client.cpp @@ -204,4 +204,26 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions); } + Azure::Core::Response BlobContainerClient::GetAccessPolicy( + const GetBlobContainerAccessPolicyOptions& options) const + { + BlobRestClient::Container::GetAccessPolicyOptions protocolLayerOptions; + protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; + return BlobRestClient::Container::GetAccessPolicy( + options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions); + } + + Azure::Core::Response BlobContainerClient::SetAccessPolicy( + const SetBlobContainerAccessPolicyOptions& options) const + { + BlobRestClient::Container::SetAccessPolicyOptions protocolLayerOptions; + protocolLayerOptions.AccessType = options.AccessType; + protocolLayerOptions.SignedIdentifiers = options.SignedIdentifiers; + protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; + protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; + return BlobRestClient::Container::SetAccessPolicy( + options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions); + } + }}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/blobs/blob_sas_builder.cpp b/sdk/storage/src/blobs/blob_sas_builder.cpp index 8e8d93b94..9f5475049 100644 --- a/sdk/storage/src/blobs/blob_sas_builder.cpp +++ b/sdk/storage/src/blobs/blob_sas_builder.cpp @@ -33,43 +33,44 @@ namespace Azure { namespace Storage { namespace Blobs { } } // namespace - void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions) + std::string BlobContainerSasPermissionsToString(BlobContainerSasPermissions permissions) { - Permissions.clear(); + std::string permissions_str; // The order matters if ((permissions & BlobContainerSasPermissions::Read) == BlobContainerSasPermissions::Read) { - Permissions += "r"; + permissions_str += "r"; } if ((permissions & BlobContainerSasPermissions::Add) == BlobContainerSasPermissions::Add) { - Permissions += "a"; + permissions_str += "a"; } if ((permissions & BlobContainerSasPermissions::Create) == BlobContainerSasPermissions::Create) { - Permissions += "c"; + permissions_str += "c"; } if ((permissions & BlobContainerSasPermissions::Write) == BlobContainerSasPermissions::Write) { - Permissions += "w"; + permissions_str += "w"; } if ((permissions & BlobContainerSasPermissions::Delete) == BlobContainerSasPermissions::Delete) { - Permissions += "d"; + permissions_str += "d"; } if ((permissions & BlobContainerSasPermissions::DeleteVersion) == BlobContainerSasPermissions::DeleteVersion) { - Permissions += "x"; + permissions_str += "x"; } if ((permissions & BlobContainerSasPermissions::List) == BlobContainerSasPermissions::List) { - Permissions += "l"; + permissions_str += "l"; } if ((permissions & BlobContainerSasPermissions::Tags) == BlobContainerSasPermissions::Tags) { - Permissions += "t"; + permissions_str += "t"; } + return permissions_str; } void BlobSasBuilder::SetPermissions(BlobSasPermissions permissions) @@ -132,7 +133,10 @@ namespace Azure { namespace Storage { namespace Blobs { { builder.AppendQuery("st", StartsOn.GetValue()); } - builder.AppendQuery("se", ExpiresOn); + if (!ExpiresOn.empty()) + { + builder.AppendQuery("se", ExpiresOn); + } if (IPRange.HasValue()) { builder.AppendQuery("sip", IPRange.GetValue()); @@ -142,7 +146,10 @@ namespace Azure { namespace Storage { namespace Blobs { builder.AppendQuery("si", Identifier); } builder.AppendQuery("sr", resource); - builder.AppendQuery("sp", Permissions); + if (!Permissions.empty()) + { + builder.AppendQuery("sp", Permissions); + } builder.AppendQuery("sig", signature, true); if (!CacheControl.empty()) { diff --git a/sdk/storage/test/blobs/blob_container_client_test.cpp b/sdk/storage/test/blobs/blob_container_client_test.cpp index 0dd5ed0ff..b31eeb9b5 100644 --- a/sdk/storage/test/blobs/blob_container_client_test.cpp +++ b/sdk/storage/test/blobs/blob_container_client_test.cpp @@ -4,6 +4,18 @@ #include "blob_container_client_test.hpp" #include "blobs/blob_sas_builder.hpp" +namespace Azure { namespace Storage { namespace Blobs { + + bool operator==( + const Azure::Storage::Blobs::BlobSignedIdentifier& lhs, + const Azure::Storage::Blobs::BlobSignedIdentifier& rhs) + { + return lhs.Id == rhs.Id && lhs.StartsOn == rhs.StartsOn && lhs.ExpiresOn == rhs.ExpiresOn + && lhs.Permissions == rhs.Permissions; + } + +}}} // namespace Azure::Storage::Blobs + namespace Azure { namespace Storage { namespace Test { std::shared_ptr @@ -225,4 +237,39 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(items, blobs); } + TEST_F(BlobContainerClientTest, AccessControlList) + { + auto container_client = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + container_client.Create(); + + Blobs::SetBlobContainerAccessPolicyOptions options; + options.AccessType = Blobs::PublicAccessType::Blob; + Blobs::BlobSignedIdentifier identifier; + identifier.Id = RandomString(64); + identifier.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1), 7); + identifier.ExpiresOn = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(1), 7); + identifier.Permissions + = Blobs::BlobContainerSasPermissionsToString(Blobs::BlobContainerSasPermissions::Read); + options.SignedIdentifiers.emplace_back(identifier); + identifier.Id = RandomString(64); + identifier.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(2), 7); + identifier.ExpiresOn = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(2), 7); + identifier.Permissions + = Blobs::BlobContainerSasPermissionsToString(Blobs::BlobContainerSasPermissions::All); + options.SignedIdentifiers.emplace_back(identifier); + + auto ret = container_client.SetAccessPolicy(options); + EXPECT_FALSE(ret->ETag.empty()); + EXPECT_FALSE(ret->LastModified.empty()); + + auto ret2 = container_client.GetAccessPolicy(); + EXPECT_EQ(ret2->ETag, ret->ETag); + EXPECT_EQ(ret2->LastModified, ret->LastModified); + EXPECT_EQ(ret2->AccessType, options.AccessType.GetValue()); + EXPECT_EQ(ret2->SignedIdentifiers, options.SignedIdentifiers); + + container_client.Delete(); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/test/blobs/blob_sas_test.cpp b/sdk/storage/test/blobs/blob_sas_test.cpp index cd61d1242..3b07a921a 100644 --- a/sdk/storage/test/blobs/blob_sas_test.cpp +++ b/sdk/storage/test/blobs/blob_sas_test.cpp @@ -340,7 +340,7 @@ namespace Azure { namespace Storage { namespace Test { builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1)); auto sasToken = builder2.ToSasQueryParameters(*keyCredential); EXPECT_THROW(verify_blob_create(sasToken), StorageError); - + auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName); EXPECT_THROW(verify_blob_create(sasToken2), StorageError); } @@ -371,6 +371,29 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_NO_THROW(verify_blob_create(sasToken2)); } + // Identifier + { + Blobs::SetBlobContainerAccessPolicyOptions options; + options.AccessType = Blobs::PublicAccessType::Blob; + Blobs::BlobSignedIdentifier identifier; + identifier.Id = RandomString(64); + identifier.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + identifier.ExpiresOn = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(60)); + identifier.Permissions + = Blobs::BlobContainerSasPermissionsToString(Blobs::BlobContainerSasPermissions::Read); + options.SignedIdentifiers.emplace_back(identifier); + m_blobContainerClient->SetAccessPolicy(options); + + Blobs::BlobSasBuilder builder2 = blobSasBuilder; + builder2.StartsOn.Reset(); + builder2.ExpiresOn.clear(); + builder2.SetPermissions(static_cast(0)); + builder2.Identifier = identifier.Id; + + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_NO_THROW(verify_blob_read(sasToken)); + } + // response headers override { Blobs::BlobHttpHeaders headers; diff --git a/sdk/storage/test/test_base.cpp b/sdk/storage/test/test_base.cpp index 69cee1ace..d76a4f126 100644 --- a/sdk/storage/test/test_base.cpp +++ b/sdk/storage/test/test_base.cpp @@ -217,7 +217,9 @@ namespace Azure { namespace Storage { namespace Test { return result; } - std::string ToISO8601(const std::chrono::system_clock::time_point& time_point) + std::string ToISO8601( + const std::chrono::system_clock::time_point& time_point, + int numDecimalDigits) { std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(time_point); struct tm ct; @@ -226,9 +228,24 @@ namespace Azure { namespace Storage { namespace Test { #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 time_str; + time_str.resize(64); + std::strftime(&time_str[0], time_str.length(), "%Y-%m-%dT%H:%M:%S", &ct); + time_str = time_str.data(); + if (numDecimalDigits != 0) + { + time_str += "."; + auto time_point_second = std::chrono::time_point_cast(time_point); + auto decimal_part = time_point - time_point_second; + uint64_t num_nanoseconds + = std::chrono::duration_cast(decimal_part).count(); + std::string decimal_part_str = std::to_string(num_nanoseconds); + decimal_part_str = std::string(9 - decimal_part_str.length(), '0') + decimal_part_str; + decimal_part_str.resize(numDecimalDigits); + time_str += decimal_part_str; + } + time_str += "Z"; + return time_str; } std::string ToRFC1123(const std::chrono::system_clock::time_point& time_point) diff --git a/sdk/storage/test/test_base.hpp b/sdk/storage/test/test_base.hpp index 118a21042..35a1bde25 100644 --- a/sdk/storage/test/test_base.hpp +++ b/sdk/storage/test/test_base.hpp @@ -61,7 +61,9 @@ 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 ToISO8601( + const std::chrono::system_clock::time_point& time_point, + int numDecimalDigits = 0); std::string ToRFC1123(const std::chrono::system_clock::time_point& time_point); }}} // namespace Azure::Storage::Test