diff --git a/sdk/tables/azure-data-tables/CMakeLists.txt b/sdk/tables/azure-data-tables/CMakeLists.txt index ee396af9c..4d64c190a 100644 --- a/sdk/tables/azure-data-tables/CMakeLists.txt +++ b/sdk/tables/azure-data-tables/CMakeLists.txt @@ -44,6 +44,7 @@ set( inc/azure/data/tables/dll_import_export.hpp inc/azure/data/tables/models.hpp inc/azure/data/tables/rtti.hpp + inc/azure/data/tables/table_audience.hpp inc/azure/data/tables.hpp inc/azure/data/tables/table_client.hpp inc/azure/data/tables/table_service_client.hpp diff --git a/sdk/tables/azure-data-tables/inc/azure/data/tables.hpp b/sdk/tables/azure-data-tables/inc/azure/data/tables.hpp index a0587a4b8..f5c47d1b4 100644 --- a/sdk/tables/azure-data-tables/inc/azure/data/tables.hpp +++ b/sdk/tables/azure-data-tables/inc/azure/data/tables.hpp @@ -11,5 +11,6 @@ #include "azure/data/tables/dll_import_export.hpp" #include "azure/data/tables/models.hpp" #include "azure/data/tables/rtti.hpp" +#include "azure/data/tables/table_audience.hpp" #include "azure/data/tables/table_client.hpp" #include "azure/data/tables/table_service_client.hpp" diff --git a/sdk/tables/azure-data-tables/inc/azure/data/tables/table_audience.hpp b/sdk/tables/azure-data-tables/inc/azure/data/tables/table_audience.hpp new file mode 100644 index 000000000..70bbc7175 --- /dev/null +++ b/sdk/tables/azure-data-tables/inc/azure/data/tables/table_audience.hpp @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include + +namespace Azure { namespace Data { namespace Tables { + /** + * @brief Audiences available for Azure Table storage service. + * + */ + class TableAudience final { + std::string m_audience; + + public: + /** + * @brief Get the audience value. + */ + std::string GetAudience() const { return m_audience; } + /** + * @brief Construct a new TablesAudience object + * + * @param tableAudience The Azure Active Directory audience to use when forming authorization + * scopes. For the Language service, this value corresponds to a URL that identifies the Azure + * cloud where the resource is located. For more information: See + * https://learn.microsoft.com/azure/storage/tables/authorize-access-azure-active-directory + */ + explicit TableAudience(std::string tableAudience) : m_audience{std::move(tableAudience)} {} + + /** + * @brief The service endpoint for a given storage account. Use this method to acquire a token + * for authorizing requests to that specific Azure Storage account and service only. + * + * @param tableAccountName The storage account name used to populate the service endpoint. + * @return The service endpoint for a given storage account. + */ + static TableAudience CreateAccountAudience(const std::string& tableAccountName) + { + return TableAudience("https://" + tableAccountName + ".table.core.windows.net/"); + } + }; +}}} // namespace Azure::Data::Tables diff --git a/sdk/tables/azure-data-tables/inc/azure/data/tables/table_client.hpp b/sdk/tables/azure-data-tables/inc/azure/data/tables/table_client.hpp index e32d6b85c..ffb704bdb 100644 --- a/sdk/tables/azure-data-tables/inc/azure/data/tables/table_client.hpp +++ b/sdk/tables/azure-data-tables/inc/azure/data/tables/table_client.hpp @@ -4,6 +4,7 @@ #pragma once #include "azure/data/tables/models.hpp" +#include "azure/data/tables/table_audience.hpp" #include #include @@ -50,6 +51,12 @@ namespace Azure { namespace Data { namespace Tables { * to prompt a challenge in order to discover the correct tenant for the resource. */ bool EnableTenantDiscovery = false; + + /** + * The Audience to use for authentication with Azure Active Directory (AAD). + * Audience will be assumed based on serviceUrl if it is not set. + */ + Azure::Nullable Audience; }; /** diff --git a/sdk/tables/azure-data-tables/src/private/tables_constants.hpp b/sdk/tables/azure-data-tables/src/private/tables_constants.hpp index b46b3ce0f..a17412a25 100644 --- a/sdk/tables/azure-data-tables/src/private/tables_constants.hpp +++ b/sdk/tables/azure-data-tables/src/private/tables_constants.hpp @@ -40,4 +40,6 @@ namespace Azure { namespace Data { namespace Tables { namespace _detail { constexpr static const char* ODataType = "odata.type"; constexpr static const char* ODataMeta = "odata.metadata"; constexpr static const char* ODataError = "odata.error"; + constexpr static const char* AudienceSuffix = ".default"; + constexpr static const char* AudienceSuffixPath = "/.default"; }}}} // namespace Azure::Data::Tables::_detail diff --git a/sdk/tables/azure-data-tables/src/table_clients.cpp b/sdk/tables/azure-data-tables/src/table_clients.cpp index 47864af02..86e9f9b4c 100644 --- a/sdk/tables/azure-data-tables/src/table_clients.cpp +++ b/sdk/tables/azure-data-tables/src/table_clients.cpp @@ -18,6 +18,17 @@ using namespace Azure::Data::Tables::_detail::Policies; using namespace Azure::Data::Tables::_detail::Xml; using namespace Azure::Data::Tables::_detail; +namespace Azure { namespace Data { namespace Tables { namespace _detail { + std::string GetDefaultScopeForAudience(const std::string& audience = "") + { + if (!audience.empty() && audience.back() == '/') + { + return audience + _detail::AudienceSuffix; + } + return audience + _detail::AudienceSuffixPath; + } +}}}} // namespace Azure::Data::Tables::_detail + TableServiceClient::TableServiceClient( const std::string& serviceUrl, const TableClientOptions& options) @@ -51,7 +62,11 @@ TableServiceClient::TableServiceClient( perRetryPolicies.emplace_back(std::make_unique()); { Azure::Core::Credentials::TokenRequestContext tokenContext; - tokenContext.Scopes.emplace_back(m_url.GetAbsoluteUrl() + "/.default"); + // if Scopes passed in are empty, use the default scope + tokenContext.Scopes.emplace_back( + options.Audience.HasValue() + ? _detail::GetDefaultScopeForAudience(options.Audience.Value().GetAudience()) + : _detail::GetDefaultScopeForAudience(m_url.GetAbsoluteUrl())); perRetryPolicies.emplace_back(std::make_unique( credential, tokenContext, newOptions.EnableTenantDiscovery)); @@ -269,7 +284,10 @@ TableClient::TableClient( perRetryPolicies.emplace_back(std::make_unique()); { Azure::Core::Credentials::TokenRequestContext tokenContext; - tokenContext.Scopes.emplace_back(m_url.GetAbsoluteUrl() + "/.default"); + tokenContext.Scopes.emplace_back( + options.Audience.HasValue() + ? _detail::GetDefaultScopeForAudience(options.Audience.Value().GetAudience()) + : _detail::GetDefaultScopeForAudience(m_url.GetAbsoluteUrl())); perRetryPolicies.emplace_back(std::make_unique( credential, tokenContext, newOptions.EnableTenantDiscovery)); diff --git a/sdk/tables/azure-data-tables/test/ut/serializers_test.hpp b/sdk/tables/azure-data-tables/test/ut/serializers_test.hpp index ad92fa68d..290e29ad8 100644 --- a/sdk/tables/azure-data-tables/test/ut/serializers_test.hpp +++ b/sdk/tables/azure-data-tables/test/ut/serializers_test.hpp @@ -3,9 +3,10 @@ // Licensed under the MIT License. #include "../src/private/serializers.hpp" -#include "azure/data/tables/table_client.hpp" #include "test/ut/test_base.hpp" +#include + namespace Azure { namespace Data { namespace Test { class SerializersTest : public Azure::Storage::Test::StorageTest { diff --git a/sdk/tables/azure-data-tables/test/ut/table_client_test.cpp b/sdk/tables/azure-data-tables/test/ut/table_client_test.cpp index 931e954b0..bb19cd8cb 100644 --- a/sdk/tables/azure-data-tables/test/ut/table_client_test.cpp +++ b/sdk/tables/azure-data-tables/test/ut/table_client_test.cpp @@ -36,6 +36,9 @@ namespace Azure { namespace Data { namespace Test { if (m_tableServiceClient.get() == nullptr) { auto clientOptions = InitStorageClientOptions(); + // set audience for only one of the clients thus both paths. + clientOptions.Audience + = Azure::Data::Tables::TableAudience::CreateAccountAudience(GetAccountName()); auto tableClientOptions = InitStorageClientOptions(); m_tableName = GetTestNameLowerCase(); @@ -84,6 +87,8 @@ namespace Azure { namespace Data { namespace Test { TEST_P(TablesClientTest, CreateTable) { + Azure::Data::Tables::TableClientOptions tableClientOptions; + auto createResponse = m_tableServiceClient->CreateTable(m_tableName); EXPECT_EQ(createResponse.Value.TableName, m_tableName); EXPECT_EQ(createResponse.Value.EditLink, "Tables('" + m_tableName + "')");