diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json index bc7c2cbc6c..114c6bea22 100644 --- a/sdk/storage/assets.json +++ b/sdk/storage/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "cpp", "TagPrefix": "cpp/storage", - "Tag": "cpp/storage_62e8551aa8" + "Tag": "cpp/storage_366c2de93d" } diff --git a/sdk/storage/azure-storage-blobs/CHANGELOG.md b/sdk/storage/azure-storage-blobs/CHANGELOG.md index e8ec1ff913..14db0f42d7 100644 --- a/sdk/storage/azure-storage-blobs/CHANGELOG.md +++ b/sdk/storage/azure-storage-blobs/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features Added +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on `BlobClientOptions` called `EnableTenantDiscovery`. If set to `true`, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. +- Added a new field `SourceAuthorization` in options for copy operations, which can be used to specify authorization for copy source. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp index ddf7ec715a..2378f24077 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp @@ -158,6 +158,13 @@ namespace Azure { namespace Storage { namespace Blobs { * API version used by this client. */ std::string ApiVersion; + + /** + * Enables tenant discovery through the authorization challenge when the client is configured to + * use a TokenCredential. When enabled, the client will attempt an initial un-authorized request + * to prompt a challenge in order to discover the correct tenant for the resource. + */ + bool EnableTenantDiscovery = false; }; /** @@ -580,6 +587,14 @@ namespace Azure { namespace Storage { namespace Blobs { * in this option. Default is to replace. */ Models::BlobCopySourceTagsMode CopySourceTagsMode; + + /** + * @brief Optional. Source authorization used to access the source file. + * The format is: \ \ + * Only Bearer type is supported. Credentials should be a valid OAuth access token to copy + * source. + */ + std::string SourceAuthorization; }; /** @@ -949,6 +964,14 @@ namespace Azure { namespace Storage { namespace Blobs { * in this option. Default is to replace. */ Models::BlobCopySourceTagsMode CopySourceTagsMode; + + /** + * @brief Optional. Source authorization used to access the source file. + * The format is: \ \ + * Only Bearer type is supported. Credentials should be a valid OAuth access token to copy + * source. + */ + std::string SourceAuthorization; }; /** @@ -997,6 +1020,14 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Optional conditions that the source must meet to perform this operation. */ SourceAccessConditions; + + /** + * @brief Optional. Source authorization used to access the source file. + * The format is: \ \ + * Only Bearer type is supported. Credentials should be a valid OAuth access token to copy + * source. + */ + std::string SourceAuthorization; }; /** @@ -1282,6 +1313,14 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Optional conditions that must be met to perform this operation. */ AppendBlobAccessConditions AccessConditions; + + /** + * @brief Optional. Source authorization used to access the source file. + * The format is: \ \ + * Only Bearer type is supported. Credentials should be a valid OAuth access token to copy + * source. + */ + std::string SourceAuthorization; }; /** @@ -1385,6 +1424,14 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Optional conditions that the source must meet to perform this operation. */ SourceAccessConditions; + + /** + * @brief Optional. Source authorization used to access the source file. + * The format is: \ \ + * Only Bearer type is supported. Credentials should be a valid OAuth access token to copy + * source. + */ + std::string SourceAuthorization; }; /** diff --git a/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp index 56568a9642..ba1cf35566 100644 --- a/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp @@ -216,6 +216,10 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.EncryptionAlgorithm = m_customerProvidedKey.Value().Algorithm.ToString(); } protocolLayerOptions.EncryptionScope = m_encryptionScope; + if (!options.SourceAuthorization.empty()) + { + protocolLayerOptions.CopySourceAuthorization = options.SourceAuthorization; + } return _detail::AppendBlobClient::AppendBlockFromUri( *m_pipeline, m_blobUrl, protocolLayerOptions, context); } diff --git a/sdk/storage/azure-storage-blobs/src/blob_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_client.cpp index b5315de83d..66c67b877c 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_client.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -87,8 +88,8 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); perRetryPolicies.emplace_back( - std::make_unique( - credential, tokenContext)); + std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery)); } perOperationPolicies.emplace_back( std::make_unique<_internal::StorageServiceVersionPolicy>(options.ApiVersion)); @@ -677,6 +678,10 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.LegalHold = options.HasLegalHold; protocolLayerOptions.EncryptionScope = m_encryptionScope; protocolLayerOptions.CopySourceTags = options.CopySourceTagsMode; + if (!options.SourceAuthorization.empty()) + { + protocolLayerOptions.CopySourceAuthorization = options.SourceAuthorization; + } return _detail::BlobClient::CopyFromUri(*m_pipeline, m_blobUrl, protocolLayerOptions, context); } diff --git a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp index a968266993..39d3313cca 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -169,9 +170,8 @@ namespace Azure { namespace Storage { namespace Blobs { { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); - tokenAuthPolicy = std::make_unique< - Azure::Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy>( - credential, tokenContext); + tokenAuthPolicy = std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery); perRetryPolicies.emplace_back(tokenAuthPolicy->Clone()); } perOperationPolicies.emplace_back( diff --git a/sdk/storage/azure-storage-blobs/src/blob_service_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_service_client.cpp index dbcab4d165..d1cac1179d 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_service_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_service_client.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -82,9 +83,8 @@ namespace Azure { namespace Storage { namespace Blobs { { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); - tokenAuthPolicy = std::make_unique< - Azure::Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy>( - credential, tokenContext); + tokenAuthPolicy = std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery); perRetryPolicies.emplace_back(tokenAuthPolicy->Clone()); } perOperationPolicies.emplace_back( diff --git a/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp index 1a4b8ff93f..f87bf76ab0 100644 --- a/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp @@ -379,6 +379,11 @@ namespace Azure { namespace Storage { namespace Blobs { } protocolLayerOptions.EncryptionScope = m_encryptionScope; protocolLayerOptions.CopySourceTags = options.CopySourceTagsMode; + if (!options.SourceAuthorization.empty()) + { + protocolLayerOptions.CopySourceAuthorization = options.SourceAuthorization; + } + return _detail::BlockBlobClient::UploadFromUri( *m_pipeline, m_blobUrl, protocolLayerOptions, context); } @@ -458,6 +463,11 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.EncryptionAlgorithm = m_customerProvidedKey.Value().Algorithm.ToString(); } protocolLayerOptions.EncryptionScope = m_encryptionScope; + if (!options.SourceAuthorization.empty()) + { + protocolLayerOptions.CopySourceAuthorization = options.SourceAuthorization; + } + return _detail::BlockBlobClient::StageBlockFromUri( *m_pipeline, m_blobUrl, protocolLayerOptions, context); } diff --git a/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp index 297795f971..9204eb12d1 100644 --- a/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp @@ -233,6 +233,10 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.EncryptionAlgorithm = m_customerProvidedKey.Value().Algorithm.ToString(); } protocolLayerOptions.EncryptionScope = m_encryptionScope; + if (!options.SourceAuthorization.empty()) + { + protocolLayerOptions.CopySourceAuthorization = options.SourceAuthorization; + } return _detail::PageBlobClient::UploadPagesFromUri( *m_pipeline, m_blobUrl, protocolLayerOptions, context); } diff --git a/sdk/storage/azure-storage-blobs/test/ut/CMakeLists.txt b/sdk/storage/azure-storage-blobs/test/ut/CMakeLists.txt index 6d67944408..f669593c80 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/CMakeLists.txt +++ b/sdk/storage/azure-storage-blobs/test/ut/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable ( azure-storage-blobs-test append_blob_client_test.cpp append_blob_client_test.hpp + bearer_token_test.cpp blob_batch_client_test.cpp blob_container_client_test.cpp blob_container_client_test.hpp diff --git a/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp index 86cde1380e..d78f073e1e 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp @@ -367,4 +367,30 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(ReadBodyStream(appendBlobClient.Download().Value.BodyStream), blockContent); } + TEST_F(AppendBlobClientTest, OAuthAppendBlockFromUri) + { + const std::vector blobContent = RandomBuffer(10); + auto contentStream = Azure::Core::IO::MemoryBodyStream(blobContent.data(), blobContent.size()); + + auto sourceBlobClient = m_blobContainerClient->GetBlockBlobClient(RandomString()); + sourceBlobClient.Upload(contentStream); + + Azure::Identity::ClientSecretCredential oauthCredential( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + Azure::Core::Credentials::TokenRequestContext requestContext; + requestContext.Scopes = {Storage::_internal::StorageScope}; + auto oauthToken = oauthCredential.GetToken(requestContext, Azure::Core::Context()); + + auto destBlobClient = GetAppendBlobClientForTest(RandomString()); + EXPECT_NO_THROW(destBlobClient.Create()); + Storage::Blobs::AppendBlockFromUriOptions options; + options.SourceAuthorization = "Bearer " + oauthToken.Token; + EXPECT_NO_THROW(destBlobClient.AppendBlockFromUri(sourceBlobClient.GetUrl(), options)); + auto properties = destBlobClient.GetProperties().Value; + EXPECT_EQ(blobContent.size(), properties.BlobSize); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-blobs/test/ut/bearer_token_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/bearer_token_test.cpp new file mode 100644 index 0000000000..9aefa96765 --- /dev/null +++ b/sdk/storage/azure-storage-blobs/test/ut/bearer_token_test.cpp @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "block_blob_client_test.hpp" + +namespace Azure { namespace Storage { namespace Test { + + TEST_F(BlockBlobClientTest, ClientSecretCredentialWorks) + { + const std::string containerName = LowercaseRandomString(); + auto containerClient = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), containerName); + auto credential = std::make_shared( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + containerClient = Blobs::BlobContainerClient( + containerClient.GetUrl(), credential, InitStorageClientOptions()); + + EXPECT_NO_THROW(containerClient.Create()); + EXPECT_NO_THROW(containerClient.Delete()); + } + + TEST_F(BlockBlobClientTest, BearerChallengeWorks) + { + Blobs::BlobClientOptions clientOptions + = InitStorageClientOptions(); + auto options = InitStorageClientOptions(); + + // With tenantId + clientOptions.EnableTenantDiscovery = true; + options.AdditionallyAllowedTenants = {"*"}; + auto blobClient = Blobs::BlobClient( + m_blockBlobClient->GetUrl(), + std::make_shared( + AadTenantId(), AadClientId(), AadClientSecret(), options), + clientOptions); + EXPECT_NO_THROW(blobClient.GetProperties()); + EXPECT_NO_THROW(ReadBodyStream(blobClient.Download().Value.BodyStream)); + + // Without tenantId + clientOptions.EnableTenantDiscovery = true; + options.AdditionallyAllowedTenants = {"*"}; + blobClient = Blobs::BlobClient( + m_blockBlobClient->GetUrl(), + std::make_shared( + "", AadClientId(), AadClientSecret(), options), + clientOptions); + EXPECT_NO_THROW(blobClient.GetProperties()); + + // With error tenantId + clientOptions.EnableTenantDiscovery = true; + options.AdditionallyAllowedTenants = {"*"}; + blobClient = Blobs::BlobClient( + m_blockBlobClient->GetUrl(), + std::make_shared( + "test", AadClientId(), AadClientSecret(), options), + clientOptions); + EXPECT_NO_THROW(blobClient.GetProperties()); + + // Disable Tenant Discovery and without tenantId + clientOptions.EnableTenantDiscovery = false; + blobClient = Blobs::BlobClient( + m_blockBlobClient->GetUrl(), + std::make_shared( + "", AadClientId(), AadClientSecret(), options), + clientOptions); + EXPECT_THROW(blobClient.GetProperties(), Azure::Core::Credentials::AuthenticationException); + + // Don't allow additional tenants + clientOptions.EnableTenantDiscovery = true; + options.AdditionallyAllowedTenants = {}; + blobClient = Blobs::BlobClient( + m_blockBlobClient->GetUrl(), + std::make_shared( + "", AadClientId(), AadClientSecret(), options), + clientOptions); + EXPECT_THROW(blobClient.GetProperties(), Azure::Core::Credentials::AuthenticationException); + } + +}}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp index 14f867abfb..799e5cc4ef 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp @@ -414,6 +414,53 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_FALSE(blobItem.Details.IncrementalCopyDestinationSnapshot.HasValue()); } + TEST_F(BlockBlobClientTest, OAuthSyncCopyFromUri) + { + auto sourceBlobClient = m_blobContainerClient->GetBlockBlobClient("source" + RandomString()); + sourceBlobClient.UploadFrom(m_blobContent.data(), m_blobContent.size()); + + Azure::Identity::ClientSecretCredential oauthCredential( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + Azure::Core::Credentials::TokenRequestContext requestContext; + requestContext.Scopes = {Storage::_internal::StorageScope}; + auto oauthToken = oauthCredential.GetToken(requestContext, Azure::Core::Context()); + + const std::string blobName = "dest" + RandomString(); + auto destBlobClient = m_blobContainerClient->GetBlockBlobClient(blobName); + + Storage::Blobs::CopyBlobFromUriOptions options; + options.SourceAuthorization = "Bearer " + oauthToken.Token; + auto res = destBlobClient.CopyFromUri(sourceBlobClient.GetUrl(), options); + EXPECT_EQ(res.RawResponse->GetStatusCode(), Azure::Core::Http::HttpStatusCode::Accepted); + EXPECT_TRUE(res.Value.ETag.HasValue()); + EXPECT_TRUE(IsValidTime(res.Value.LastModified)); + EXPECT_FALSE(res.Value.CopyId.empty()); + EXPECT_EQ(res.Value.CopyStatus, Azure::Storage::Blobs::Models::CopyStatus::Success); + + auto downloadResult = destBlobClient.Download(); + EXPECT_FALSE(downloadResult.Value.Details.CopyId.Value().empty()); + EXPECT_FALSE(downloadResult.Value.Details.CopySource.Value().empty()); + EXPECT_TRUE( + downloadResult.Value.Details.CopyStatus.Value() + == Azure::Storage::Blobs::Models::CopyStatus::Success); + EXPECT_FALSE(downloadResult.Value.Details.CopyProgress.Value().empty()); + EXPECT_TRUE(IsValidTime(downloadResult.Value.Details.CopyCompletedOn.Value())); + + auto blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Copy); + EXPECT_FALSE(blobItem.Details.CopyId.Value().empty()); + EXPECT_FALSE(blobItem.Details.CopySource.Value().empty()); + EXPECT_TRUE( + blobItem.Details.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Success); + EXPECT_FALSE(blobItem.Details.CopyProgress.Value().empty()); + EXPECT_TRUE(IsValidTime(blobItem.Details.CopyCompletedOn.Value())); + ASSERT_TRUE(blobItem.Details.IsIncrementalCopy.HasValue()); + EXPECT_FALSE(blobItem.Details.IsIncrementalCopy.Value()); + EXPECT_FALSE(blobItem.Details.IncrementalCopyDestinationSnapshot.HasValue()); + } + TEST_F(BlockBlobClientTest, SyncCopyFromUriEncryptionScope) { Blobs::BlobClientOptions clientOptions; @@ -709,6 +756,41 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(res.Value.UncommittedBlocks.empty()); } + TEST_F(BlockBlobClientTest, OAuthStageBlockFromUri) + { + auto srcBlobClient = *m_blockBlobClient; + + auto destClient = GetBlockBlobClientForTest(RandomString()); + const std::string blockId1 = Base64EncodeText("0"); + + Azure::Identity::ClientSecretCredential oauthCredential( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + Azure::Core::Credentials::TokenRequestContext requestContext; + requestContext.Scopes = {Storage::_internal::StorageScope}; + auto oauthToken = oauthCredential.GetToken(requestContext, Azure::Core::Context()); + + Storage::Blobs::StageBlockFromUriOptions options; + options.SourceAuthorization = "Bearer " + oauthToken.Token; + + destClient.StageBlockFromUri(blockId1, srcBlobClient.GetUrl() + GetSas()); + Blobs::GetBlockListOptions options2; + options2.ListType = Blobs::Models::BlockListType::All; + auto res = destClient.GetBlockList(options2); + ASSERT_FALSE(res.Value.UncommittedBlocks.empty()); + EXPECT_EQ(res.Value.UncommittedBlocks[0].Name, blockId1); + EXPECT_EQ(res.Value.UncommittedBlocks[0].Size, static_cast(m_blobContent.size())); + + destClient.CommitBlockList({ + blockId1, + }); + res = destClient.GetBlockList(options2); + EXPECT_EQ(res.Value.BlobSize, static_cast(m_blobContent.size())); + EXPECT_TRUE(res.Value.UncommittedBlocks.empty()); + } + TEST_F(BlockBlobClientTest, DeleteIfExists) { auto blobClient = GetBlockBlobClientForTest(RandomString()); @@ -1313,6 +1395,47 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(destBlobClient.GetTags().Value, srcTags); } + TEST_F(BlockBlobClientTest, OAuthUploadFromUri) + { + auto srcBlobClient = *m_blockBlobClient; + std::vector blobContent = RandomBuffer(100); + srcBlobClient.UploadFrom(blobContent.data(), blobContent.size()); + + const std::vector blobMd5 + = Azure::Core::Cryptography::Md5Hash().Final(blobContent.data(), blobContent.size()); + const std::vector blobCrc64 + = Azure::Storage::Crc64Hash().Final(blobContent.data(), blobContent.size()); + + Azure::Identity::ClientSecretCredential oauthCredential( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + Azure::Core::Credentials::TokenRequestContext requestContext; + requestContext.Scopes = {Storage::_internal::StorageScope}; + auto oauthToken = oauthCredential.GetToken(requestContext, Azure::Core::Context()); + + Storage::Blobs::UploadBlockBlobFromUriOptions options; + options.SourceAuthorization = "Bearer " + oauthToken.Token; + auto destBlobClient = GetBlockBlobClientForTest(RandomString() + "dest"); + auto uploadFromUriResult = destBlobClient.UploadFromUri(srcBlobClient.GetUrl(), options); + EXPECT_TRUE(uploadFromUriResult.Value.ETag.HasValue()); + EXPECT_TRUE(IsValidTime(uploadFromUriResult.Value.LastModified)); + EXPECT_TRUE(uploadFromUriResult.Value.VersionId.HasValue()); + EXPECT_TRUE(uploadFromUriResult.Value.IsServerEncrypted); + ASSERT_TRUE(uploadFromUriResult.Value.TransactionalContentHash.HasValue()); + if (uploadFromUriResult.Value.TransactionalContentHash.Value().Algorithm == HashAlgorithm::Md5) + { + EXPECT_EQ(uploadFromUriResult.Value.TransactionalContentHash.Value().Value, blobMd5); + } + else if ( + uploadFromUriResult.Value.TransactionalContentHash.Value().Algorithm + == HashAlgorithm::Crc64) + { + EXPECT_EQ(uploadFromUriResult.Value.TransactionalContentHash.Value().Value, blobCrc64); + } + } + TEST_F(BlockBlobClientTest, SetGetTagsWithLeaseId) { auto blobClient = *m_blockBlobClient; diff --git a/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp index b346ade3fd..d0586868e6 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp @@ -219,6 +219,31 @@ namespace Azure { namespace Storage { namespace Test { pageBlobClient.Download().Value.BodyStream->ReadToEnd()); } + TEST_F(PageBlobClientTest, OAuthUploadFromUri) + { + auto pageBlobClient = *m_pageBlobClient; + + auto pageBlobClient2 = GetPageBlobClientTestForTest(RandomString()); + pageBlobClient2.Create(m_blobContent.size()); + + Azure::Identity::ClientSecretCredential oauthCredential( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + Azure::Core::Credentials::TokenRequestContext requestContext; + requestContext.Scopes = {Storage::_internal::StorageScope}; + auto oauthToken = oauthCredential.GetToken(requestContext, Azure::Core::Context()); + + Storage::Blobs::UploadPagesFromUriOptions options; + options.SourceAuthorization = "Bearer " + oauthToken.Token; + pageBlobClient2.UploadPagesFromUri( + 0, pageBlobClient.GetUrl(), {0, static_cast(m_blobContent.size())}, options); + EXPECT_EQ( + pageBlobClient2.Download().Value.BodyStream->ReadToEnd(), + pageBlobClient.Download().Value.BodyStream->ReadToEnd()); + } + TEST_F(PageBlobClientTest, StartCopyIncremental) { auto pageBlobClient = *m_pageBlobClient; diff --git a/sdk/storage/azure-storage-common/CMakeLists.txt b/sdk/storage/azure-storage-common/CMakeLists.txt index 10a70f2688..7011ca325e 100644 --- a/sdk/storage/azure-storage-common/CMakeLists.txt +++ b/sdk/storage/azure-storage-common/CMakeLists.txt @@ -52,6 +52,7 @@ set( inc/azure/storage/common/internal/file_io.hpp inc/azure/storage/common/internal/reliable_stream.hpp inc/azure/storage/common/internal/shared_key_policy.hpp + inc/azure/storage/common/internal/storage_bearer_token_authentication_policy.hpp inc/azure/storage/common/internal/storage_per_retry_policy.hpp inc/azure/storage/common/internal/storage_service_version_policy.hpp inc/azure/storage/common/internal/storage_switch_to_secondary_policy.hpp @@ -70,6 +71,7 @@ set( src/file_io.cpp src/reliable_stream.cpp src/shared_key_policy.cpp + src/storage_bearer_token_authentication_policy.cpp src/storage_credential.cpp src/storage_exception.cpp src/storage_per_retry_policy.cpp diff --git a/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/storage_bearer_token_authentication_policy.hpp b/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/storage_bearer_token_authentication_policy.hpp new file mode 100644 index 0000000000..ca32557526 --- /dev/null +++ b/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/storage_bearer_token_authentication_policy.hpp @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include +#include + +namespace Azure { namespace Storage { namespace _internal { + + class StorageBearerTokenAuthenticationPolicy final + : public Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy { + public: + /** + * @brief Construct a Storage Bearer Token challenge authentication policy. + * + * @param credential An #Azure::Core::TokenCredential to use with this policy. + * @param tokenRequestContext A context to get the token in. + * @param enableTenantDiscovery Enables tenant discovery through the authorization challenge. + */ + explicit StorageBearerTokenAuthenticationPolicy( + std::shared_ptr credential, + Azure::Core::Credentials::TokenRequestContext tokenRequestContext, + bool enableTenantDiscovery) + : BearerTokenAuthenticationPolicy(std::move(credential), tokenRequestContext), + m_scopes(tokenRequestContext.Scopes), m_safeTenantId(tokenRequestContext.TenantId), + m_enableTenantDiscovery(enableTenantDiscovery) + { + } + + ~StorageBearerTokenAuthenticationPolicy() override {} + + std::unique_ptr Clone() const override + { + return std::unique_ptr(new StorageBearerTokenAuthenticationPolicy(*this)); + } + + private: + struct SafeTenantId + { + public: + explicit SafeTenantId(std::string tenantId) : m_tenantId(std::move(tenantId)) {} + + SafeTenantId(const SafeTenantId& other) : m_tenantId(other.Get()) {} + + std::string Get() const + { + std::shared_lock lock(m_tenantIdMutex); + return m_tenantId; + } + + void Set(const std::string& tenantId) + { + std::unique_lock lock(m_tenantIdMutex); + m_tenantId = tenantId; + } + + private: + std::string m_tenantId; + mutable std::shared_timed_mutex m_tenantIdMutex; + }; + + std::vector m_scopes; + mutable SafeTenantId m_safeTenantId; + bool m_enableTenantDiscovery; + + std::unique_ptr AuthorizeAndSendRequest( + Azure::Core::Http::Request& request, + Azure::Core::Http::Policies::NextHttpPolicy& nextPolicy, + Azure::Core::Context const& context) const override; + + bool AuthorizeRequestOnChallenge( + std::string const& challenge, + Azure::Core::Http ::Request& request, + Azure::Core::Context const& context) const override; + }; + +}}} // namespace Azure::Storage::_internal diff --git a/sdk/storage/azure-storage-common/src/storage_bearer_token_authentication_policy.cpp b/sdk/storage/azure-storage-common/src/storage_bearer_token_authentication_policy.cpp new file mode 100644 index 0000000000..ba21b98bc7 --- /dev/null +++ b/sdk/storage/azure-storage-common/src/storage_bearer_token_authentication_policy.cpp @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/storage/common/internal/storage_bearer_token_authentication_policy.hpp" + +#include "azure/storage/common/internal/constants.hpp" + +#include + +namespace Azure { namespace Storage { namespace _internal { + + std::unique_ptr + StorageBearerTokenAuthenticationPolicy::AuthorizeAndSendRequest( + Azure::Core::Http::Request& request, + Azure::Core::Http::Policies::NextHttpPolicy& nextPolicy, + Azure::Core::Context const& context) const + { + std::string tenantId = m_safeTenantId.Get(); + if (!tenantId.empty() || !m_enableTenantDiscovery) + { + Azure::Core::Credentials::TokenRequestContext tokenRequestContext; + tokenRequestContext.Scopes = m_scopes; + tokenRequestContext.TenantId = tenantId; + AuthenticateAndAuthorizeRequest(request, tokenRequestContext, context); + } + return nextPolicy.Send(request, context); + } + + bool StorageBearerTokenAuthenticationPolicy::AuthorizeRequestOnChallenge( + std::string const& challenge, + Azure::Core::Http ::Request& request, + Azure::Core::Context const& context) const + { + if (!m_enableTenantDiscovery) + { + return false; + } + std::string authorizationUri + = Azure::Core::Credentials::_internal::AuthorizationChallengeParser::GetChallengeParameter( + challenge, "Bearer", "authorization_uri"); + + // tenantId should be the guid as seen in this example: + // https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize + std::string path = Azure::Core::Url(authorizationUri).GetPath(); + std::string tenantId = path.substr(0, path.find('/')); + m_safeTenantId.Set(tenantId); + + Azure::Core::Credentials::TokenRequestContext tokenRequestContext; + tokenRequestContext.Scopes = m_scopes; + tokenRequestContext.TenantId = tenantId; + AuthenticateAndAuthorizeRequest(request, tokenRequestContext, context); + return true; + } + +}}} // namespace Azure::Storage::_internal diff --git a/sdk/storage/azure-storage-common/test/ut/CMakeLists.txt b/sdk/storage/azure-storage-common/test/ut/CMakeLists.txt index 7463af2e7d..ad56497ff7 100644 --- a/sdk/storage/azure-storage-common/test/ut/CMakeLists.txt +++ b/sdk/storage/azure-storage-common/test/ut/CMakeLists.txt @@ -16,7 +16,6 @@ SetUpTestProxy("sdk/storage") add_executable ( azure-storage-common-test - bearer_token_test.cpp crypt_functions_test.cpp metadata_test.cpp storage_credential_test.cpp diff --git a/sdk/storage/azure-storage-common/test/ut/bearer_token_test.cpp b/sdk/storage/azure-storage-common/test/ut/bearer_token_test.cpp deleted file mode 100644 index adf026af6a..0000000000 --- a/sdk/storage/azure-storage-common/test/ut/bearer_token_test.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "test_base.hpp" - -namespace Azure { namespace Storage { namespace Test { - - TEST_F(StorageTest, ClientSecretCredentialWorks) - { - const std::string containerName = LowercaseRandomString(); - auto containerClient = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( - StandardStorageConnectionString(), containerName); - auto credential = std::make_shared( - AadTenantId(), AadClientId(), AadClientSecret(), GetTokenCredentialOptions()); - containerClient = Blobs::BlobContainerClient( - containerClient.GetUrl(), credential, InitStorageClientOptions()); - - EXPECT_NO_THROW(containerClient.Create()); - EXPECT_NO_THROW(containerClient.Delete()); - } - -}}} // namespace Azure::Storage::Test \ No newline at end of file diff --git a/sdk/storage/azure-storage-files-datalake/CHANGELOG.md b/sdk/storage/azure-storage-files-datalake/CHANGELOG.md index a8ba5d55fc..11f0f4d678 100644 --- a/sdk/storage/azure-storage-files-datalake/CHANGELOG.md +++ b/sdk/storage/azure-storage-files-datalake/CHANGELOG.md @@ -4,6 +4,9 @@ ### Features Added +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on `DataLakeClientOptions` called `EnableTenantDiscovery`. If set to `true`, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp index d753d5ecb6..263b72d844 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp @@ -130,6 +130,13 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { * @brief Holds the customer provided key used when making requests. */ Azure::Nullable CustomerProvidedKey; + + /** + * Enables tenant discovery through the authorization challenge when the client is configured to + * use a TokenCredential. When enabled, the client will attempt an initial un-authorized request + * to prompt a challenge in order to discover the correct tenant for the resource. + */ + bool EnableTenantDiscovery = false; }; /** diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp index e4961ab5bb..fa44f0273d 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -98,8 +99,8 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); perRetryPolicies.emplace_back( - std::make_unique( - credential, tokenContext)); + std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery)); } perOperationPolicies.emplace_back( std::make_unique<_internal::StorageServiceVersionPolicy>(options.ApiVersion)); diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp index 3c36cbb05d..67f2d6992a 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -96,8 +97,8 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); perRetryPolicies.emplace_back( - std::make_unique( - credential, tokenContext)); + std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery)); } perOperationPolicies.emplace_back( std::make_unique<_internal::StorageServiceVersionPolicy>(options.ApiVersion)); diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_service_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_service_client.cpp index 0b09394503..096525605c 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_service_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_service_client.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -92,8 +93,8 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); perRetryPolicies.emplace_back( - std::make_unique( - credential, tokenContext)); + std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery)); } perOperationPolicies.emplace_back( std::make_unique<_internal::StorageServiceVersionPolicy>(options.ApiVersion)); diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_utilities.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_utilities.cpp index 8249d35d2e..ced748cb91 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_utilities.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_utilities.cpp @@ -97,6 +97,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { nam = _detail::GetBlobUrlFromUrl(options.SecondaryHostForRetryReads); blobOptions.ApiVersion = options.ApiVersion; blobOptions.CustomerProvidedKey = options.CustomerProvidedKey; + blobOptions.EnableTenantDiscovery = options.EnableTenantDiscovery; return blobOptions; } diff --git a/sdk/storage/azure-storage-files-shares/CHANGELOG.md b/sdk/storage/azure-storage-files-shares/CHANGELOG.md index ce9905a075..a9d2517ed6 100644 --- a/sdk/storage/azure-storage-files-shares/CHANGELOG.md +++ b/sdk/storage/azure-storage-files-shares/CHANGELOG.md @@ -4,6 +4,11 @@ ### Features Added +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on `ShareClientOptions` called `EnableTenantDiscovery`. If set to `true`, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. +- Added a new field `SourceAuthorization` in options for copy operations, which can be used to specify authorization for copy source. +- Added a new field `ContentType` in `RenameFileOptions`. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp index c992a919b2..75f7db4ef9 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp @@ -295,6 +295,11 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { * A name-value pair to associate with a file storage object. */ Storage::Metadata Metadata; + + /** + * Content type to set on the File. + */ + Azure::Nullable ContentType; }; /** @@ -716,6 +721,14 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { * or the last write time currently associated with the file should be preserved. */ Azure::Nullable FileLastWrittenMode; + + /** + * @brief Optional. Source authorization used to access the source file. + * The format is: \ \ + * Only Bearer type is supported. Credentials should be a valid OAuth access token to copy + * source. + */ + std::string SourceAuthorization; }; /** diff --git a/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp index 5489f9a4cd..93c7599747 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp @@ -290,6 +290,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { protocolLayerOptions.AllowTrailingDot = m_allowTrailingDot; protocolLayerOptions.AllowSourceTrailingDot = m_allowSourceTrailingDot; protocolLayerOptions.FileRequestIntent = m_shareTokenIntent; + protocolLayerOptions.FileContentType = options.ContentType; auto response = _detail::FileClient::Rename( *m_pipeline, destinationFileUrl, protocolLayerOptions, context); diff --git a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp index 41a1bfbb25..7a8ab02e24 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp @@ -1268,6 +1268,10 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { + std::string("-") + std::to_string(sourceRange.Offset + sourceRange.Length.Value() - 1); protocolLayerOptions.AllowTrailingDot = m_allowTrailingDot; protocolLayerOptions.AllowSourceTrailingDot = m_allowSourceTrailingDot; + if (!options.SourceAuthorization.empty()) + { + protocolLayerOptions.CopySourceAuthorization = options.SourceAuthorization; + } return _detail::FileClient::UploadRangeFromUri( *m_pipeline, m_shareFileUrl, protocolLayerOptions, context); diff --git a/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp index 44204b0d6f..4e8683ceb1 100644 --- a/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp @@ -174,6 +174,7 @@ namespace Azure { namespace Storage { namespace Test { properties.LastWrittenOn = std::chrono::system_clock::now(); properties.Attributes = Files::Shares::Models::FileAttributes::None; renameOptions.SmbProperties = properties; + renameOptions.ContentType = "application/x-binary"; auto newFileClient = baseDirectoryClient .RenameFile(oldFilename, baseDirectoryName + "/" + newFilename, renameOptions) @@ -183,6 +184,7 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(oldFileClient.GetProperties(), StorageException); EXPECT_EQ(renameOptions.Metadata, newProperties.Metadata); EXPECT_EQ(properties.Attributes, newProperties.SmbProperties.Attributes); + EXPECT_EQ(renameOptions.ContentType.Value(), newProperties.HttpHeaders.ContentType); } // diff directory diff --git a/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp index 01742d19f7..b62dae24e3 100644 --- a/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp @@ -1021,6 +1021,64 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(FileShareFileClientTest, OAuthUploadRangeFromUri) + { + size_t fileSize = 1 * 1024; + std::string containerName = LowercaseRandomString(); + std::string blobName = RandomString(); + std::vector blobContent = RandomBuffer(fileSize); + auto memBodyStream = Core::IO::MemoryBodyStream(blobContent); + + auto containerClient = Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), + containerName, + InitStorageClientOptions()); + containerClient.Create(); + auto sourceBlobClient = containerClient.GetBlockBlobClient(blobName); + sourceBlobClient.Upload(memBodyStream); + + auto destFileClient + = m_shareClient->GetRootDirectoryClient().GetFileClient(RandomString() + "f2"); + destFileClient.Create(fileSize * 4); + Azure::Core::Http::HttpRange sourceRange; + Azure::Core::Http::HttpRange destRange; + sourceRange.Length = fileSize; + destRange.Length = fileSize; + + // Get oauth token of source file + Azure::Identity::ClientSecretCredential oauthCredential( + AadTenantId(), + AadClientId(), + AadClientSecret(), + InitStorageClientOptions()); + Azure::Core::Credentials::TokenRequestContext requestContext; + requestContext.Scopes = {Storage::_internal::StorageScope}; + auto oauthToken = oauthCredential.GetToken(requestContext, Azure::Core::Context()); + + Files::Shares::UploadFileRangeFromUriOptions options; + options.SourceAuthorization = "Bearer " + oauthToken.Token; + Files::Shares::Models::UploadFileRangeFromUriResult uploadResult; + EXPECT_NO_THROW( + uploadResult + = destFileClient + .UploadRangeFromUri(destRange.Offset, sourceBlobClient.GetUrl(), sourceRange, options) + .Value); + + Files::Shares::Models::DownloadFileResult result; + Files::Shares::DownloadFileOptions downloadOptions; + downloadOptions.Range = destRange; + EXPECT_NO_THROW(result = destFileClient.Download(downloadOptions).Value); + auto resultBuffer = result.BodyStream->ReadToEnd(Core::Context()); + EXPECT_EQ(blobContent, resultBuffer); + Files::Shares::Models::GetFileRangeListResult getRangeResult; + EXPECT_NO_THROW(getRangeResult = destFileClient.GetRangeList().Value); + EXPECT_EQ(1U, getRangeResult.Ranges.size()); + EXPECT_TRUE(getRangeResult.Ranges[0].Length.HasValue()); + EXPECT_EQ(static_cast(fileSize), getRangeResult.Ranges[0].Length.Value()); + + EXPECT_NO_THROW(containerClient.Delete()); + } + TEST_F(FileShareFileClientTest, AllowTrailingDot) { const std::string fileName = RandomString(); diff --git a/sdk/storage/azure-storage-queues/CHANGELOG.md b/sdk/storage/azure-storage-queues/CHANGELOG.md index 44d6e6f37e..5d55cf1b5e 100644 --- a/sdk/storage/azure-storage-queues/CHANGELOG.md +++ b/sdk/storage/azure-storage-queues/CHANGELOG.md @@ -3,6 +3,10 @@ ## 12.1.0-beta.1 (Unreleased) ### Features Added +- Bumped up API version to `2019-12-12`. +- Bumped up SAS token version to `2019-12-12`. +- TenantId can now be discovered through the service challenge response, when using a TokenCredential for authorization. + - A new property is now available on `QueueClientOptions` called `EnableTenantDiscovery`. If set to `true`, the client will attempt an initial unauthorized request to the service to prompt a challenge containing the tenantId hint. ### Breaking Changes diff --git a/sdk/storage/azure-storage-queues/inc/azure/storage/queues/queue_options.hpp b/sdk/storage/azure-storage-queues/inc/azure/storage/queues/queue_options.hpp index e3eab01e79..2586446cf5 100644 --- a/sdk/storage/azure-storage-queues/inc/azure/storage/queues/queue_options.hpp +++ b/sdk/storage/azure-storage-queues/inc/azure/storage/queues/queue_options.hpp @@ -78,6 +78,13 @@ namespace Azure { namespace Storage { namespace Queues { * API version used by this client. */ ServiceVersion ApiVersion{_detail::ApiVersion}; + + /** + * Enables tenant discovery through the authorization challenge when the client is configured to + * use a TokenCredential. When enabled, the client will attempt an initial un-authorized request + * to prompt a challenge in order to discover the correct tenant for the resource. + */ + bool EnableTenantDiscovery = false; }; /** diff --git a/sdk/storage/azure-storage-queues/inc/azure/storage/queues/rest_client.hpp b/sdk/storage/azure-storage-queues/inc/azure/storage/queues/rest_client.hpp index 55f3c097df..250a0f11c6 100644 --- a/sdk/storage/azure-storage-queues/inc/azure/storage/queues/rest_client.hpp +++ b/sdk/storage/azure-storage-queues/inc/azure/storage/queues/rest_client.hpp @@ -26,7 +26,7 @@ namespace Azure { namespace Storage { namespace Queues { /** * The version used for the operations to Azure storage services. */ - constexpr static const char* ApiVersion = "2018-03-28"; + constexpr static const char* ApiVersion = "2019-12-12"; } // namespace _detail namespace Models { /** diff --git a/sdk/storage/azure-storage-queues/src/queue_client.cpp b/sdk/storage/azure-storage-queues/src/queue_client.cpp index 439e4b5000..0f4477ca59 100644 --- a/sdk/storage/azure-storage-queues/src/queue_client.cpp +++ b/sdk/storage/azure-storage-queues/src/queue_client.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -75,8 +76,8 @@ namespace Azure { namespace Storage { namespace Queues { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); perRetryPolicies.emplace_back( - std::make_unique( - credential, tokenContext)); + std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery)); } perOperationPolicies.emplace_back( std::make_unique<_internal::StorageServiceVersionPolicy>(options.ApiVersion.ToString())); diff --git a/sdk/storage/azure-storage-queues/src/queue_sas_builder.cpp b/sdk/storage/azure-storage-queues/src/queue_sas_builder.cpp index 57c74aa6e9..62fb0f82bf 100644 --- a/sdk/storage/azure-storage-queues/src/queue_sas_builder.cpp +++ b/sdk/storage/azure-storage-queues/src/queue_sas_builder.cpp @@ -9,7 +9,7 @@ namespace Azure { namespace Storage { namespace Sas { namespace { - constexpr static const char* SasVersion = "2018-03-28"; + constexpr static const char* SasVersion = "2019-12-12"; } void QueueSasBuilder::SetPermissions(QueueSasPermissions permissions) diff --git a/sdk/storage/azure-storage-queues/src/queue_service_client.cpp b/sdk/storage/azure-storage-queues/src/queue_service_client.cpp index 3c990f6d92..39f2ef5919 100644 --- a/sdk/storage/azure-storage-queues/src/queue_service_client.cpp +++ b/sdk/storage/azure-storage-queues/src/queue_service_client.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -73,8 +74,8 @@ namespace Azure { namespace Storage { namespace Queues { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(_internal::StorageScope); perRetryPolicies.emplace_back( - std::make_unique( - credential, tokenContext)); + std::make_unique<_internal::StorageBearerTokenAuthenticationPolicy>( + credential, tokenContext, options.EnableTenantDiscovery)); } perOperationPolicies.emplace_back( std::make_unique<_internal::StorageServiceVersionPolicy>(options.ApiVersion.ToString())); diff --git a/sdk/storage/azure-storage-queues/src/rest_client.cpp b/sdk/storage/azure-storage-queues/src/rest_client.cpp index 9e2d30af8f..4befa24c82 100644 --- a/sdk/storage/azure-storage-queues/src/rest_client.cpp +++ b/sdk/storage/azure-storage-queues/src/rest_client.cpp @@ -189,7 +189,7 @@ namespace Azure { namespace Storage { namespace Queues { request.SetHeader("Content-Length", std::to_string(requestBody.Length())); request.GetUrl().AppendQueryParameter("restype", "service"); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Accepted) @@ -209,7 +209,7 @@ namespace Azure { namespace Storage { namespace Queues { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("restype", "service"); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); (void)options; auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); @@ -446,7 +446,7 @@ namespace Azure { namespace Storage { namespace Queues { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("restype", "service"); request.GetUrl().AppendQueryParameter("comp", "stats"); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); (void)options; auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); @@ -546,7 +546,7 @@ namespace Azure { namespace Storage { namespace Queues { _internal::UrlEncodeQueryParameter( ListQueuesIncludeFlagsToString(options.Include.Value()))); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) @@ -669,7 +669,7 @@ namespace Azure { namespace Storage { namespace Queues { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (!(httpStatusCode == Core::Http::HttpStatusCode::Created @@ -687,7 +687,7 @@ namespace Azure { namespace Storage { namespace Queues { const Core::Context& context) { auto request = Core::Http::Request(Core::Http::HttpMethod::Delete, url); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); (void)options; auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); @@ -706,7 +706,7 @@ namespace Azure { namespace Storage { namespace Queues { { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("comp", "metadata"); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); (void)options; auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); @@ -737,7 +737,7 @@ namespace Azure { namespace Storage { namespace Queues { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) @@ -755,7 +755,7 @@ namespace Azure { namespace Storage { namespace Queues { { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("comp", "acl"); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); (void)options; auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); @@ -898,7 +898,7 @@ namespace Azure { namespace Storage { namespace Queues { request.SetHeader("Content-Type", "application/xml; charset=UTF-8"); request.SetHeader("Content-Length", std::to_string(requestBody.Length())); request.GetUrl().AppendQueryParameter("comp", "acl"); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) @@ -926,7 +926,7 @@ namespace Azure { namespace Storage { namespace Queues { request.GetUrl().AppendQueryParameter( "visibilitytimeout", std::to_string(options.Visibilitytimeout.Value())); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) @@ -1052,7 +1052,7 @@ namespace Azure { namespace Storage { namespace Queues { const Core::Context& context) { auto request = Core::Http::Request(Core::Http::HttpMethod::Delete, url); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); (void)options; auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); @@ -1094,7 +1094,7 @@ namespace Azure { namespace Storage { namespace Queues { request.GetUrl().AppendQueryParameter( "messagettl", std::to_string(options.MessageTimeToLive.Value())); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Created) @@ -1203,7 +1203,7 @@ namespace Azure { namespace Storage { namespace Queues { request.GetUrl().AppendQueryParameter( "numofmessages", std::to_string(options.NumberOfMessages.Value())); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) @@ -1332,7 +1332,7 @@ namespace Azure { namespace Storage { namespace Queues { } request.GetUrl().AppendQueryParameter( "visibilitytimeout", std::to_string(options.Visibilitytimeout)); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) @@ -1358,7 +1358,7 @@ namespace Azure { namespace Storage { namespace Queues { request.GetUrl().AppendQueryParameter( "popreceipt", _internal::UrlEncodeQueryParameter(options.PopReceipt)); } - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) @@ -1382,7 +1382,7 @@ namespace Azure { namespace Storage { namespace Queues { } request.GetUrl().AppendQueryParameter( "visibilitytimeout", std::to_string(options.Visibilitytimeout)); - request.SetHeader("x-ms-version", "2018-03-28"); + request.SetHeader("x-ms-version", "2019-12-12"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) diff --git a/sdk/storage/azure-storage-queues/swagger/README.md b/sdk/storage/azure-storage-queues/swagger/README.md index 81b301ad58..6f3d22258e 100644 --- a/sdk/storage/azure-storage-queues/swagger/README.md +++ b/sdk/storage/azure-storage-queues/swagger/README.md @@ -77,8 +77,13 @@ directive: "name": "ApiVersion", "modelAsString": false }, - "enum": ["2018-03-28"] + "enum": ["2019-12-12"], + "description": "The version used for the operations to Azure storage services." }; + - from: swagger-document + where: $.parameters + transform: > + $.ApiVersionParameter.enum[0] = "2019-12-12"; ``` ### Rename Operations