diff --git a/sdk/core/azure-core/inc/azure/core/internal/environment.hpp b/sdk/core/azure-core/inc/azure/core/internal/environment.hpp index ca4b3f6d5..9893d60c3 100644 --- a/sdk/core/azure-core/inc/azure/core/internal/environment.hpp +++ b/sdk/core/azure-core/inc/azure/core/internal/environment.hpp @@ -12,6 +12,12 @@ namespace Azure { namespace Core { namespace _internal { ~Environment() = delete; public: + static std::string GetVariable(const std::string& name) { return GetVariable(name.c_str()); } + static void SetVariable(const std::string& name, const std::string& value) + { + SetVariable(name.c_str(), value.c_str()); + } + static std::string GetVariable(const char* name); static void SetVariable(const char* name, const char* value); }; diff --git a/sdk/core/azure-core/inc/azure/core/internal/strings.hpp b/sdk/core/azure-core/inc/azure/core/internal/strings.hpp index 4582d97e8..4886a9208 100644 --- a/sdk/core/azure-core/inc/azure/core/internal/strings.hpp +++ b/sdk/core/azure-core/inc/azure/core/internal/strings.hpp @@ -38,11 +38,13 @@ namespace Azure { namespace Core { namespace _internal { return IsDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } - static constexpr bool IsAlphaNumeric(char c) noexcept + static constexpr bool IsAlpha(char c) noexcept { - return IsDigit(c) || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } + static constexpr bool IsAlphaNumeric(char c) noexcept { return IsDigit(c) || IsAlpha(c); } + static constexpr bool IsSpace(char c) noexcept { return c == ' ' || (c >= '\t' && c <= '\r'); } static constexpr bool IsPrintable(char c) noexcept { return c >= ' ' && c <= '~'; } diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 4f89d07a7..866552e12 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -2,8 +2,11 @@ ## 1.13.1 (2025-09-11) -### Bugs Fixed +### Features Added +- Added a constructor overload for `DefaultAzureCredential` with a boolean parameter to indicate whether to throw an exception if `AZURE_TOKEN_CREDENTIALS` environment variable doesn't have a value. + +### Bugs Fixed - Fixed IMDS token requests for managed identities, which were broken by an invalid URL path in 1.12.0-beta.1. (A community contribution, courtesy of _[chewi](https://github.com/chewi)_) ### Acknowledgments diff --git a/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp index a555e432a..9dfe5925f 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp @@ -62,6 +62,19 @@ namespace Azure { namespace Identity { */ explicit DefaultAzureCredential(Core::Credentials::TokenCredentialOptions const& options); + /** + * @brief Constructs `%DefaultAzureCredential`. + * + * @param requireCredentialSpecifierEnvVarValue Throw an exception if `AZURE_TOKEN_CREDENTIALS` + * environment variable is not set. + * + * @param options Generic Token Credential Options. + * + */ + explicit DefaultAzureCredential( + bool requireCredentialSpecifierEnvVarValue, + Core::Credentials::TokenCredentialOptions const& options = {}); + /** * @brief Destructs `%DefaultAzureCredential`. * diff --git a/sdk/identity/azure-identity/src/default_azure_credential.cpp b/sdk/identity/azure-identity/src/default_azure_credential.cpp index b167e9a76..e2ba76220 100644 --- a/sdk/identity/azure-identity/src/default_azure_credential.cpp +++ b/sdk/identity/azure-identity/src/default_azure_credential.cpp @@ -26,7 +26,18 @@ using Azure::Core::_internal::StringExtensions; using Azure::Core::Diagnostics::Logger; using Azure::Identity::_detail::IdentityLog; +namespace { +constexpr auto CredentialSpecifierEnvVarName = "AZURE_TOKEN_CREDENTIALS"; +} // namespace + DefaultAzureCredential::DefaultAzureCredential( + Core::Credentials::TokenCredentialOptions const& options) + : DefaultAzureCredential(false, options) +{ +} + +DefaultAzureCredential::DefaultAzureCredential( + bool requireCredentialSpecifierEnvVarValue, Core::Credentials::TokenCredentialOptions const& options) : TokenCredential("DefaultAzureCredential") { @@ -77,10 +88,16 @@ DefaultAzureCredential::DefaultAzureCredential( [](auto options) { return std::make_shared(options); }}, }; - constexpr auto envVarName = "AZURE_TOKEN_CREDENTIALS"; - const auto envVarValue = Environment::GetVariable(envVarName); + const auto envVarValue = Environment::GetVariable(CredentialSpecifierEnvVarName); const auto trimmedEnvVarValue = StringExtensions::Trim(envVarValue); + if (requireCredentialSpecifierEnvVarValue && trimmedEnvVarValue.empty()) + { + throw AuthenticationException( + GetCredentialName() + ": '" + CredentialSpecifierEnvVarName + + "' environment variable is empty."); + } + bool specificCred = false; if (!trimmedEnvVarValue.empty()) { @@ -92,8 +109,8 @@ DefaultAzureCredential::DefaultAzureCredential( specificCred = true; IdentityLog::Write( IdentityLog::Level::Verbose, - GetCredentialName() + ": '" + envVarName + "' environment variable is set to '" - + envVarValue + GetCredentialName() + ": '" + CredentialSpecifierEnvVarName + + "' environment variable is set to '" + envVarValue + "', therefore credential chain will only contain single credential: " + cred.CredentialName + '.'); credentialChain.emplace_back(cred.Create(options)); @@ -143,7 +160,8 @@ DefaultAzureCredential::DefaultAzureCredential( } } - const auto logMsg = GetCredentialName() + ": '" + envVarName + "' environment variable is " + const auto logMsg = GetCredentialName() + ": '" + CredentialSpecifierEnvVarName + + "' environment variable is " + (envVarValue.empty() ? "not set" : ("set to '" + envVarValue + "'")) + ((devCredCount > 0) ? (", therefore " + devCredNames + " will " + (isProd ? "NOT " : "") @@ -177,10 +195,13 @@ DefaultAzureCredential::DefaultAzureCredential( } throw AuthenticationException( - GetCredentialName() + ": Invalid value '" + envVarValue + "' for the '" + envVarName + GetCredentialName() + ": Invalid value '" + envVarValue + "' for the '" + + CredentialSpecifierEnvVarName + "' environment variable. Allowed values are 'dev', 'prod'" + allowedCredNames - + " (case insensitive). " - "It is also valid to not have the environment variable defined."); + + " (case insensitive)." + + (requireCredentialSpecifierEnvVarValue + ? "" + : " It is also valid to not have the environment variable defined.")); } } } diff --git a/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp b/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp index 8e1cbd067..4b1a84eec 100644 --- a/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp @@ -522,3 +522,9 @@ TEST_P(LogMessagesForSpecificCredential, ) Logger::SetListener(nullptr); } + +TEST(DefaultAzureCredential, RequireCredentialSpecifierEnvVarValue) +{ + EXPECT_THROW( + static_cast(std::make_unique(true)), AuthenticationException); +}