From 68be8b456802bf6da34bdf16c5f4ba9ff019d57d Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk <41349689+antkmsft@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:34:47 -0700 Subject: [PATCH] Identity: Improve diagnosability (#4744) * Identity: Improve diagnosability * Update sdk/identity/azure-identity/src/azure_cli_credential.cpp Co-authored-by: Larry Osterman * GCC fix * Mac fix * More agressive sanitizing * cspell * minor CI fixes * Improve * More tests * min/max values test --------- Co-authored-by: Anton Kolesnyk Co-authored-by: Larry Osterman --- .../src/azure_cli_credential.cpp | 16 +- .../src/token_credential_impl.cpp | 297 ++- .../test/ut/azure_cli_credential_test.cpp | 50 + .../test/ut/token_credential_impl_test.cpp | 1802 +++++++++++++++++ 4 files changed, 2143 insertions(+), 22 deletions(-) diff --git a/sdk/identity/azure-identity/src/azure_cli_credential.cpp b/sdk/identity/azure-identity/src/azure_cli_credential.cpp index 06f2bc8a3..70ba2e6e4 100644 --- a/sdk/identity/azure-identity/src/azure_cli_credential.cpp +++ b/sdk/identity/azure-identity/src/azure_cli_credential.cpp @@ -8,6 +8,7 @@ #include "private/token_credential_impl.hpp" #include +#include #include #include #include @@ -47,6 +48,7 @@ using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; using Azure::Core::Credentials::TokenCredentialOptions; using Azure::Core::Credentials::TokenRequestContext; +using Azure::Core::Json::_internal::json; using Azure::Identity::AzureCliCredentialOptions; using Azure::Identity::_detail::IdentityLog; using Azure::Identity::_detail::TenantIdResolver; @@ -160,10 +162,18 @@ AccessToken AzureCliCredential::GetToken( return TokenCredentialImpl::ParseToken( azCliResult, "accessToken", "expiresIn", "expiresOn"); } - catch (std::exception const&) + catch (json::exception const&) { - // Throw the az command output (error message) - // limited to 250 characters (250 has no special meaning). + // json::exception gets thrown when a string we provided for parsing is not a json object. + // It should not get thrown if the string is a valid JSON, but there are specific problems + // with the token JSON object - missing property, failure to parse a specific property etc. + // I.e. this means that the az commnd has rather printed some error message + // (such as "ERROR: Please run az login to setup account.") instead of producing a JSON + // object output. In this case, we want the exception to be thrown with the output from the + // command (which is likely the error message) and not with the details of the exception + // that was thrown from ParseToken() (which most likely will be "Unexpected token ..."). + // So, we limit the az command output (error message) limited to 250 characters so it is not + // too long, and throw that. throw std::runtime_error(azCliResult.substr(0, 250)); } } diff --git a/sdk/identity/azure-identity/src/token_credential_impl.cpp b/sdk/identity/azure-identity/src/token_credential_impl.cpp index 8a61d2f12..a0be7befd 100644 --- a/sdk/identity/azure-identity/src/token_credential_impl.cpp +++ b/sdk/identity/azure-identity/src/token_credential_impl.cpp @@ -3,27 +3,33 @@ #include "private/token_credential_impl.hpp" +#include "private/identity_log.hpp" #include "private/package_version.hpp" #include +#include #include #include -#include +#include +#include using Azure::Identity::_detail::TokenCredentialImpl; +using Azure::Identity::_detail::IdentityLog; using Azure::Identity::_detail::PackageVersion; using Azure::DateTime; using Azure::Core::Context; using Azure::Core::Url; using Azure::Core::_internal::PosixTimeConverter; +using Azure::Core::_internal::StringExtensions; using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; using Azure::Core::Credentials::TokenCredentialOptions; using Azure::Core::Http::HttpStatusCode; using Azure::Core::Http::RawResponse; +using Azure::Core::Json::_internal::json; TokenCredentialImpl::TokenCredentialImpl(TokenCredentialOptions const& options) : m_httpPipeline(options, "identity", PackageVersion::ToString(), {}, {}) @@ -141,10 +147,48 @@ AccessToken TokenCredentialImpl::GetToken( } namespace { -[[noreturn]] void ThrowJsonPropertyError(std::string const& propertyName) +std::string const ParseTokenLogPrefix = "TokenCredentialImpl::ParseToken(): "; + +constexpr std::int64_t MaxExpirationInSeconds = 2147483647; // int32 max (68+ years) +constexpr std::int64_t MaxPosixTimestamp = 253402300799; // 9999-12-31T23:59:59 + +std::int64_t ParseNumericExpiration( + std::string const& numericString, + std::int64_t maxValue, + std::int64_t minValue = 0) { + auto const asNumber = std::stoll(numericString); + return (asNumber >= minValue && asNumber <= maxValue && std::to_string(asNumber) == numericString) + ? static_cast(asNumber) + : throw std::exception(); +} + +std::string TokenAsDiagnosticString( + json const& jsonObject, + std::string const& accessTokenPropertyName, + std::string const& expiresInPropertyName, + std::string const& expiresOnPropertyName); + +[[noreturn]] void ThrowJsonPropertyError( + std::string const& failedPropertyName, + json const& jsonObject, + std::string const& accessTokenPropertyName, + std::string const& expiresInPropertyName, + std::string const& expiresOnPropertyName) +{ + if (IdentityLog::ShouldWrite(IdentityLog::Level::Verbose)) + { + IdentityLog::Write( + IdentityLog::Level::Verbose, + ParseTokenLogPrefix + + TokenAsDiagnosticString( + jsonObject, accessTokenPropertyName, expiresInPropertyName, expiresOnPropertyName)); + } + throw std::runtime_error( - std::string("Token JSON object: can't find or parse \'") + propertyName + "\' property."); + "Token JSON object: can't find or parse \'" + failedPropertyName + + "\' property.\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting)."); } } // namespace @@ -154,12 +198,29 @@ AccessToken TokenCredentialImpl::ParseToken( std::string const& expiresInPropertyName, std::string const& expiresOnPropertyName) { - auto const parsedJson = Azure::Core::Json::_internal::json::parse(jsonString); + json parsedJson; + try + { + parsedJson = Azure::Core::Json::_internal::json::parse(jsonString); + } + catch (json::exception const&) + { + IdentityLog::Write( + IdentityLog::Level::Verbose, + ParseTokenLogPrefix + "Cannot parse the string '" + jsonString + "' as JSON."); + + throw; + } if (!parsedJson.contains(accessTokenPropertyName) || !parsedJson[accessTokenPropertyName].is_string()) { - ThrowJsonPropertyError(accessTokenPropertyName); + ThrowJsonPropertyError( + accessTokenPropertyName, + parsedJson, + accessTokenPropertyName, + expiresInPropertyName, + expiresOnPropertyName); } AccessToken accessToken; @@ -172,11 +233,24 @@ AccessToken TokenCredentialImpl::ParseToken( if (expiresIn.is_number_unsigned()) { - // 'expires_in' as number (seconds until expiration) - accessToken.ExpiresOn - += std::chrono::seconds(expiresIn.get()); + try + { + // 'expires_in' as number (seconds until expiration) + auto const value = expiresIn.get(); + if (value <= MaxExpirationInSeconds) + { + static_assert( + MaxExpirationInSeconds <= std::numeric_limits::max(), + "Can safely cast to int32"); - return accessToken; + accessToken.ExpiresOn += std::chrono::seconds(static_cast(value)); + return accessToken; + } + } + catch (std::exception const&) + { + // expiresIn.get() has thrown, we may throw later. + } } if (expiresIn.is_string()) @@ -184,13 +258,18 @@ AccessToken TokenCredentialImpl::ParseToken( try { // 'expires_in' as numeric string (seconds until expiration) - accessToken.ExpiresOn += std::chrono::seconds(std::stoi(expiresIn.get())); + static_assert( + MaxExpirationInSeconds <= std::numeric_limits::max(), + "Can safely cast to int32"); + + accessToken.ExpiresOn += std::chrono::seconds(static_cast( + ParseNumericExpiration(expiresIn.get(), MaxExpirationInSeconds))); return accessToken; } catch (std::exception const&) { - // stoi() has thrown, we may throw later. + // ParseNumericExpiration() has thrown, we may throw later. } } } @@ -198,7 +277,12 @@ AccessToken TokenCredentialImpl::ParseToken( if (expiresOnPropertyName.empty()) { // 'expires_in' is undefined, 'expires_on' is not expected. - ThrowJsonPropertyError(expiresInPropertyName); + ThrowJsonPropertyError( + expiresInPropertyName, + parsedJson, + accessTokenPropertyName, + expiresInPropertyName, + expiresOnPropertyName); } if (parsedJson.contains(expiresOnPropertyName)) @@ -207,11 +291,20 @@ AccessToken TokenCredentialImpl::ParseToken( if (expiresOn.is_number_unsigned()) { - // 'expires_on' as number (posix time representing an absolute timestamp) - accessToken.ExpiresOn - = PosixTimeConverter::PosixTimeToDateTime(expiresOn.get()); - - return accessToken; + try + { + // 'expires_on' as number (posix time representing an absolute timestamp) + auto const value = expiresOn.get(); + if (value <= MaxPosixTimestamp) + { + accessToken.ExpiresOn = PosixTimeConverter::PosixTimeToDateTime(value); + return accessToken; + } + } + catch (std::exception const&) + { + // expiresIn.get() has thrown, we may throw later. + } } if (expiresOn.is_string()) @@ -224,7 +317,8 @@ AccessToken TokenCredentialImpl::ParseToken( }), std::function([](auto const& s) { // 'expires_on' as numeric string (posix time representing an absolute timestamp) - return PosixTimeConverter::PosixTimeToDateTime(std::stoll(s)); + return PosixTimeConverter::PosixTimeToDateTime( + ParseNumericExpiration(s, MaxPosixTimestamp)); }), std::function([](auto const& s) { // 'expires_on' as RFC1123 date string (absolute timestamp) @@ -245,5 +339,170 @@ AccessToken TokenCredentialImpl::ParseToken( } } - ThrowJsonPropertyError(expiresOnPropertyName); + ThrowJsonPropertyError( + expiresOnPropertyName, + parsedJson, + accessTokenPropertyName, + expiresInPropertyName, + expiresOnPropertyName); } + +namespace { +std::string PrintSanitizedJsonObject(json const& jsonObject, bool printString, int depth = 0) +{ + if (jsonObject.is_null() || jsonObject.is_boolean() || jsonObject.is_number() + || (printString && jsonObject.is_string())) + { + return jsonObject.dump(); + } + + if (jsonObject.is_string()) + { + auto const stringValue = jsonObject.get(); + for (auto const& parse : { + std::function([&]() -> std::string { + return (StringExtensions::LocaleInvariantCaseInsensitiveEqual(stringValue, "null") + || StringExtensions::LocaleInvariantCaseInsensitiveEqual(stringValue, "true") + || StringExtensions::LocaleInvariantCaseInsensitiveEqual( + stringValue, "false")) + ? stringValue + : std::string{}; + }), + std::function([&]() -> std::string { + return DateTime::Parse(stringValue, DateTime::DateFormat::Rfc3339) + .ToString(DateTime::DateFormat::Rfc3339); + }), + std::function([&]() -> std::string { + return std::to_string(ParseNumericExpiration( + stringValue, + std::numeric_limits::max(), + std::numeric_limits::min())); + }), + std::function([&]() -> std::string { + return DateTime::Parse(stringValue, DateTime::DateFormat::Rfc1123) + .ToString(DateTime::DateFormat::Rfc1123); + }), + }) + { + try + { + auto const parsedValueAsString = parse(); + if (!parsedValueAsString.empty()) + { + return '"' + parsedValueAsString + '"'; + } + } + catch (std::exception const&) + { + } + } + + return "string.length=" + std::to_string(stringValue.length()); + } + + if (jsonObject.is_array()) + { + return "[...]"; + } + + if (jsonObject.is_object()) + { + if (depth == 0) + { + std::string objectValue{"{"}; + char const* delimiter = ""; + for (auto const& property : jsonObject.items()) + { + objectValue += delimiter; + objectValue += '\'' + property.key() + "': "; + objectValue += PrintSanitizedJsonObject(property.value(), false, depth + 1); + delimiter = ", "; + } + return objectValue + '}'; + } + else + { + return "{...}"; + } + } + + return "?"; // LCOV_EXCL_LINE +} + +std::string TokenAsDiagnosticString( + json const& jsonObject, + std::string const& accessTokenPropertyName, + std::string const& expiresInPropertyName, + std::string const& expiresOnPropertyName) +{ + std::string result = "Please report an issue with the following details:\nToken JSON"; + + if (!jsonObject.is_object()) + { + result += " is not an object (" + PrintSanitizedJsonObject(jsonObject, false) + ")"; + } + else + { + result += ": Access token property ('" + accessTokenPropertyName + "'): "; + if (!jsonObject.contains(accessTokenPropertyName)) + { + result += "undefined"; + } + else + { + auto const& accessTokenProperty = jsonObject[accessTokenPropertyName]; + result += accessTokenProperty.is_string() + ? ("string.length=" + std::to_string(accessTokenProperty.get().length())) + : PrintSanitizedJsonObject(accessTokenProperty, false); + } + + for (auto const& p : { + std::pair{"relative", &expiresInPropertyName}, + std::pair{"absolute", &expiresOnPropertyName}, + }) + { + result += ", "; + result += p.first; + result += " expiration property ('" + *p.second + "'): "; + + if (!jsonObject.contains(*p.second)) + { + result += "undefined"; + } + else + { + result += PrintSanitizedJsonObject(jsonObject[*p.second], true); + } + } + + std::map otherProperties; + for (auto const& property : jsonObject.items()) + { + if (property.key() != accessTokenPropertyName && property.key() != expiresInPropertyName + && property.key() != expiresOnPropertyName) + { + otherProperties[property.key()] = property.value(); + } + } + + result += ", "; + if (otherProperties.empty()) + { + result += "and there are no other properties"; + } + else + { + result += "other properties"; + const char* delimiter = ": "; + for (auto const& property : otherProperties) + { + result += delimiter; + result += "'" + property.first + "': " + PrintSanitizedJsonObject(property.second, false); + delimiter = ", "; + } + } + } + + return result + '.'; +} +} // namespace diff --git a/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp b/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp index 5e143493c..514bd8050 100644 --- a/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp @@ -359,4 +359,54 @@ TEST(AzureCliCredential, StrictIso8601TimeFormat) token.ExpiresOn, DateTime::Parse("2022-08-24T00:43:08.000000Z", DateTime::DateFormat::Rfc3339)); } + +TEST(AzureCliCredential, Diagnosability) +{ + { + AzureCliTestCredential const azCliCred( + EchoCommand("az is not recognized as an internal or external command, " + "operable program or batch file.")); + + TokenRequestContext trc; + trc.Scopes.push_back("https://storage.azure.com/.default"); + try + { + static_cast(azCliCred.GetToken(trc, {})); + } + catch (AuthenticationException const& e) + { + std::string const expectedMsgStart + = "AzureCliCredential didn't get the token: " + "\"az is not recognized as an internal or external command, " + "operable program or batch file."; + + std::string actualMsgStart = e.what(); + actualMsgStart.resize(expectedMsgStart.length()); + + // It is enough to compare StartsWith() and not deal with + // the entire string due to '/n' and '/r/n' differences. + EXPECT_EQ(actualMsgStart, expectedMsgStart); + } + } + + { + AzureCliTestCredential const azCliCred(EchoCommand("{\"property\":\"value\"}")); + + TokenRequestContext trc; + trc.Scopes.push_back("https://storage.azure.com/.default"); + try + { + static_cast(azCliCred.GetToken(trc, {})); + } + catch (AuthenticationException const& e) + { + EXPECT_EQ( + e.what(), + std::string("AzureCliCredential didn't get the token: " + "\"Token JSON object: can't find or parse 'accessToken' property.\n" + "See Azure::Core::Diagnostics::Logger for details " + "(https://aka.ms/azsdk/cpp/identity/troubleshooting).\"")); + } + } +} #endif // not UWP diff --git a/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp b/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp index 6a44b8027..cdb8a5a26 100644 --- a/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp +++ b/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp @@ -4,6 +4,9 @@ #include "credential_test_helper.hpp" #include "private/token_credential_impl.hpp" +#include +#include + #include #include @@ -663,3 +666,1802 @@ TEST(TokenCredentialImpl, ExpirationFormats) EXPECT_EQ(response42.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); EXPECT_EQ(response43.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); } + +TEST(TokenCredentialImpl, MaxValues) +{ + // 'exp_in' negative + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_in\":-1}", "token", "exp_in", "exp_at")), + std::exception); + + // 'exp_in' zero + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_in\":0}", "token", "exp_in", "exp_at"))); + + // 'exp_in' == int32 max + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_in\":2147483647}", "token", "exp_in", "exp_at"))); + + // 'exp_in' > int32 max + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_in\":2147483648}", "token", "exp_in", "exp_at")), + std::exception); + + // 'exp_at' negative + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_at\":-1}", "token", "exp_in", "exp_at")), + std::exception); + + // 'exp_at' zero + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_at\":0}", "token", "exp_in", "exp_at"))); + + // 'exp_at' == '9999-12-31 23:59:59' + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_at\":253402300799}", "token", "exp_in", "exp_at"))); + + // 'exp_at' > '9999-12-31 23:59:59' + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"exp_at\":253402300800}", "token", "exp_in", "exp_at")), + std::exception); +} + +TEST(TokenCredentialImpl, Diagnosability) +{ + using Azure::Core::Diagnostics::Logger; + using Azure::Core::Json::_internal::json; + using LogMsgVec = std::vector>; + + Logger::SetLevel(Logger::Level::Verbose); + + // When AzureCliCredential passes an output from the command it ran. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "ERROR: Please run az login to setup account.", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")), + json::exception); + + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Cannot parse the string 'ERROR: Please run az login to setup account.' as JSON."); + + Logger::SetListener(nullptr); + } + + // Empty JSON object. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{}", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): undefined, " + "relative expiration property ('TokenExpiresInSeconds'): undefined, " + "absolute expiration property ('TokenExpiresAtTime'): undefined, " + "and there are no other properties."); + + Logger::SetListener(nullptr); + } + + // Access token is not a string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":{}}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): {}, " + "relative expiration property ('TokenExpiresInSeconds'): undefined, " + "absolute expiration property ('TokenExpiresAtTime'): undefined, " + "and there are no other properties."); + + Logger::SetListener(nullptr); + } + + // Token is ok, but expiration is missing. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"ACCESSTOKEN\"}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=11, " + "relative expiration property ('TokenExpiresInSeconds'): undefined, " + "absolute expiration property ('TokenExpiresAtTime'): undefined, " + "and there are no other properties."); + + Logger::SetListener(nullptr); + } + + // Token is ok, but relative expiration can't be parsed, and absolute expiration is missing. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"ACCESSTOKEN\"," + "\"TokenExpiresInSeconds\":\"one\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + {})); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresInSeconds' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=11, " + "relative expiration property ('TokenExpiresInSeconds'): \"one\", " + "absolute expiration property (''): undefined, " + "and there are no other properties."); + + Logger::SetListener(nullptr); + } + + // Token is ok, relative expiration can't be parsed, absolute expiration is null, + // and one other property has RFC3339 timestamp string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"ACCESSTOKEN\"," + "\"TokenExpiresInSeconds\":1.5," + "\"TokenExpiresAtTime\":null," + "\"token_expires_at_time\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=11, " + "relative expiration property ('TokenExpiresInSeconds'): 1.5, " + "absolute expiration property ('TokenExpiresAtTime'): null, " + "other properties: 'token_expires_at_time': \"Sun, 22 Nov 3333 04:05:06 GMT\"."); + + Logger::SetListener(nullptr); + } + + // Token is ok, relative expiration is missing, absolute expiration can't be parsed, + // And one other property has RFC3339 timestamp string, while the other is a number. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"ACCESSTOKEN\"," + "\"TokenExpiresAtTime\":\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 " + "Zulu\"," + "\"token_expires_at_time\":\"Sun, 22 Nov 3333 04:05:06 GMT\"," + "\"token_expires_in_seconds\":45" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=11, " + "relative expiration property ('TokenExpiresInSeconds'): undefined, " + "absolute expiration property ('TokenExpiresAtTime'): " + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\", " + "other properties: 'token_expires_at_time': \"Sun, 22 Nov 3333 04:05:06 GMT\", " + "'token_expires_in_seconds': 45."); + + Logger::SetListener(nullptr); + } + + // Token is ok, relative expiration can't be parsed, absolute expiration can't be parsed, + // One other property has RFC3339 timestamp string, another is a number, third is a string, + // fourth is array. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"ACCESSTOKEN\"," + "\"TokenExpiresInSeconds\":-1," + "\"TokenExpiresAtTime\":true," + "\"tokenexpiresattime\":\"Sun, 22 Nov 3333 04:05:06 GMT\"," + "\"token_expires_in_seconds\":45," + "\"token_for_accessing\":\"ACCESSTOKEN\"," + "\"array\":[1, 2, 3]" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=11, " + "relative expiration property ('TokenExpiresInSeconds'): -1, " + "absolute expiration property ('TokenExpiresAtTime'): true, " + "other properties: " + "'array': [...], " + "'token_expires_in_seconds': 45, " + "'token_for_accessing': string.length=11, " + "'tokenexpiresattime': \"Sun, 22 Nov 3333 04:05:06 GMT\"."); + + Logger::SetListener(nullptr); + } + + // No log message is emitted when parse is successful. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"ACCESSTOKEN\"," + "\"TokenExpiresInSeconds\":3600," + "\"TokenExpiresAtTime\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime"))); + + EXPECT_EQ(log.size(), 0U); + + Logger::SetListener(nullptr); + } + + // Not sanitizing nulls. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":null," + "\"TokenExpiresInSeconds\":null," + "\"TokenExpiresAtTime\":null," + "\"OtherProperty\":null" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): null, " + "relative expiration property ('TokenExpiresInSeconds'): null, " + "absolute expiration property ('TokenExpiresAtTime'): null, " + "other properties: 'OtherProperty': null."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing boolean true. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":true," + "\"TokenExpiresInSeconds\":true," + "\"TokenExpiresAtTime\":true," + "\"OtherProperty\":true" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): true, " + "relative expiration property ('TokenExpiresInSeconds'): true, " + "absolute expiration property ('TokenExpiresAtTime'): true, " + "other properties: 'OtherProperty': true."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing boolean false. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":false," + "\"TokenExpiresInSeconds\":false," + "\"TokenExpiresAtTime\":false," + "\"OtherProperty\":false" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): false, " + "relative expiration property ('TokenExpiresInSeconds'): false, " + "absolute expiration property ('TokenExpiresAtTime'): false, " + "other properties: 'OtherProperty': false."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing int64 max. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":9223372036854775807," + "\"TokenExpiresInSeconds\":9223372036854775807," // > int32 max (68+ years) + "\"TokenExpiresAtTime\":9223372036854775807," // > year 9999 + "\"OtherProperty\":9223372036854775807" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): 9223372036854775807, " + "relative expiration property ('TokenExpiresInSeconds'): 9223372036854775807, " + "absolute expiration property ('TokenExpiresAtTime'): 9223372036854775807, " + "other properties: 'OtherProperty': 9223372036854775807."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing int64 min. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":-9223372036854775808," + "\"TokenExpiresInSeconds\":-9223372036854775808," // would fail to parse as unsigned + "\"TokenExpiresAtTime\":-9223372036854775808," // would fail to parse as unsigned + "\"OtherProperty\":-9223372036854775808" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): -9223372036854775808, " + "relative expiration property ('TokenExpiresInSeconds'): -9223372036854775808, " + "absolute expiration property ('TokenExpiresAtTime'): -9223372036854775808, " + "other properties: 'OtherProperty': -9223372036854775808."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing double. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":-1.25," + "\"TokenExpiresInSeconds\":-1.25," + "\"TokenExpiresAtTime\":-1.25," + "\"OtherProperty\":-1.25" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): -1.25, " + "relative expiration property ('TokenExpiresInSeconds'): -1.25, " + "absolute expiration property ('TokenExpiresAtTime'): -1.25, " + "other properties: 'OtherProperty': -1.25."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing double (scientific notation). + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":-9.00719925e+15," + "\"TokenExpiresInSeconds\":-9.00719925e+15," + "\"TokenExpiresAtTime\":-9.00719925E+15," + "\"OtherProperty\":-9.00719925E+15" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): -9.00719925e+15, " + "relative expiration property ('TokenExpiresInSeconds'): -9.00719925e+15, " + "absolute expiration property ('TokenExpiresAtTime'): -9.00719925e+15, " + "other properties: 'OtherProperty': -9.00719925e+15."); + + Logger::SetListener(nullptr); + } + + // Sanitizing arrays. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":[1, 2, 3]," + "\"TokenExpiresInSeconds\":[1, 2, 3]," + "\"TokenExpiresAtTime\":[1, 2, 3]," + "\"OtherProperty\":[1, 2, 3]" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): [...], " + "relative expiration property ('TokenExpiresInSeconds'): [...], " + "absolute expiration property ('TokenExpiresAtTime'): [...], " + "other properties: 'OtherProperty': [...]."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that say "null" (case insensitive), except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"null\"," + "\"TokenExpiresInSeconds\":\"NULL\"," + "\"TokenExpiresAtTime\":\"Null\"," + "\"OtherProperty\":\"nUlL\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=4, " + "relative expiration property ('TokenExpiresInSeconds'): \"NULL\", " + "absolute expiration property ('TokenExpiresAtTime'): \"Null\", " + "other properties: 'OtherProperty': \"nUlL\"."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that say "true" (case insensitive), except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"true\"," + "\"TokenExpiresInSeconds\":\"TRUE\"," + "\"TokenExpiresAtTime\":\"True\"," + "\"OtherProperty\":\"tRuE\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=4, " + "relative expiration property ('TokenExpiresInSeconds'): \"TRUE\", " + "absolute expiration property ('TokenExpiresAtTime'): \"True\", " + "other properties: 'OtherProperty': \"tRuE\"."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that say "false" (case insensitive), except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"false\"," + "\"TokenExpiresInSeconds\":\"FALSE\"," + "\"TokenExpiresAtTime\":\"False\"," + "\"OtherProperty\":\"fAlSe\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=5, " + "relative expiration property ('TokenExpiresInSeconds'): \"FALSE\", " + "absolute expiration property ('TokenExpiresAtTime'): \"False\", " + "other properties: 'OtherProperty': \"fAlSe\"."); + + Logger::SetListener(nullptr); + } + + // Sanitizing other strings, except for the expiration properties. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"maybe\"," + "\"TokenExpiresInSeconds\":\"maybe\"," + "\"TokenExpiresAtTime\":\"maybe\"," + "\"OtherProperty\":\"maybe\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=5, " + "relative expiration property ('TokenExpiresInSeconds'): \"maybe\", " + "absolute expiration property ('TokenExpiresAtTime'): \"maybe\", " + "other properties: 'OtherProperty': string.length=5."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that represent int64 max, except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"9223372036854775807\"," + "\"TokenExpiresInSeconds\":\"9223372036854775807\"," // > int32 max (68+ years) + "\"TokenExpiresAtTime\":\"9223372036854775807\"," // > year 9999 + "\"OtherProperty\":\"9223372036854775807\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=19, " + "relative expiration property ('TokenExpiresInSeconds'): \"9223372036854775807\", " + "absolute expiration property ('TokenExpiresAtTime'): \"9223372036854775807\", " + "other properties: 'OtherProperty': \"9223372036854775807\"."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that represent int64, except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"-9223372036854775808\"," + "\"TokenExpiresInSeconds\":\"-9223372036854775808\"," // would fail to parse as being < 0 + "\"TokenExpiresAtTime\":\"-9223372036854775808\"," // would fail to parse as being < 0 + "\"OtherProperty\":\"-9223372036854775808\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=20, " + "relative expiration property ('TokenExpiresInSeconds'): \"-9223372036854775808\", " + "absolute expiration property ('TokenExpiresAtTime'): \"-9223372036854775808\", " + "other properties: 'OtherProperty': \"-9223372036854775808\"."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that represent double, except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"-1.25\"," + "\"TokenExpiresInSeconds\":\"-1.25\"," + "\"TokenExpiresAtTime\":\"-1.25\"," + "\"OtherProperty\":\"-1.25\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=5, " + "relative expiration property ('TokenExpiresInSeconds'): \"-1.25\", " + "absolute expiration property ('TokenExpiresAtTime'): \"-1.25\", " + "other properties: 'OtherProperty': string.length=5."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that represent double (scientific notation), except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"-9.00719925e+15\"," + "\"TokenExpiresInSeconds\":\"-9.00719925e+15\"," + "\"TokenExpiresAtTime\":\"-9.00719925E+15\"," + "\"OtherProperty\":\"-9.00719925e+15\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=15, " + "relative expiration property ('TokenExpiresInSeconds'): \"-9.00719925e+15\", " + "absolute expiration property ('TokenExpiresAtTime'): \"-9.00719925E+15\", " + "other properties: 'OtherProperty': string.length=15."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that represent datetime (RFC3339), except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"3333-11-22T04:05:06.000Z\"," + "\"TokenExpiresInSeconds\":\"3333-11-22T04:05:06.000Z\"," + "\"TokenExpiresAtTime\":\"fail3333-11-22T04:05:06.000Z\"," + "\"OtherProperty\":\"3333-11-22T04:05:06.000Z\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=24, " + "relative expiration property ('TokenExpiresInSeconds'): \"3333-11-22T04:05:06.000Z\", " + "absolute expiration property ('TokenExpiresAtTime'): \"fail3333-11-22T04:05:06.000Z\", " + "other properties: 'OtherProperty': \"3333-11-22T04:05:06Z\"."); + + Logger::SetListener(nullptr); + } + + // Not sanitizing strings that represent datetime (RFC1123), except for access token. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"Sun, 22 Nov 3333 04:05:06 GMT\"," + "\"TokenExpiresInSeconds\":\"Sun, 22 Nov 3333 04:05:06 GMT\"," + "\"TokenExpiresAtTime\":\"failSun, 22 Nov 3333 04:05:06 GMT\"," + "\"OtherProperty\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=29, " + "relative expiration property ('TokenExpiresInSeconds'): " + "\"Sun, 22 Nov 3333 04:05:06 GMT\", " + "absolute expiration property ('TokenExpiresAtTime'): " + "\"failSun, 22 Nov 3333 04:05:06 GMT\", " + "other properties: 'OtherProperty': " + "\"Sun, 22 Nov 3333 04:05:06 GMT\"."); + + Logger::SetListener(nullptr); + } + + // More explicitly, do sanitize unknown datetime format, except for the expiration properties. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":" + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\"," + "\"TokenExpiresInSeconds\":" + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\"," + "\"TokenExpiresAtTime\":" + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\"," + "\"OtherProperty\":" + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=61, " + "relative expiration property ('TokenExpiresInSeconds'): " + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\", " + "absolute expiration property ('TokenExpiresAtTime'): " + "\"Sunday, November 22nd of 3333 A.D. at 6 seconds past 405 Zulu\", " + "other properties: 'OtherProperty': string.length=61."); + + Logger::SetListener(nullptr); + } + + // std::stoll() et al. have a leak: "Discards any whitespace characters (as identified by calling + // std::isspace) until the first non-whitespace character is found, then takes as many characters + // as possible to form a valid base-n (where n=base) integer number representation and converts + // them to an integer value". This means that if we verify that the string can be parsed as long + // long and then simply print jsonObject.dump() thinking it is safe, we may print any string that + // starts with a number. Instead, we only want to print the integer that was parsed as a string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":\"1337LEAK\"," + "\"TokenExpiresInSeconds\":\"1337LEAK\"," + "\"TokenExpiresAtTime\":\"1337LEAK\"," + "\"OtherProperty\":\"1337LEAK\"" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenExpiresAtTime' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): string.length=8, " + "relative expiration property ('TokenExpiresInSeconds'): \"1337LEAK\", " + "absolute expiration property ('TokenExpiresAtTime'): \"1337LEAK\", " + "other properties: 'OtherProperty': string.length=8."); + + Logger::SetListener(nullptr); + } + + // Sanitizing JSON objects. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "{\"TokenForAccessing\":" + "{" + "\"a\":null,\"b\":true,\"c\":false,\"d\":1,\"e\":\"E\",\"f\":{\"x\":true,\"y\":false}," + "\"g\":\"null\",\"h\":\"true\",\"i\":\"false\",\"j\":\"1\",\"k\":[1,2,3]," + "\"l\":\"3333-11-22T04:05:06.000Z\"," + "\"m\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}," + "\"TokenExpiresInSeconds\":" + "{" + "\"a\":null,\"b\":true,\"c\":false,\"d\":1,\"e\":\"E\",\"f\":{\"x\":true,\"y\":false}," + "\"g\":\"null\",\"h\":\"true\",\"i\":\"false\",\"j\":\"1\",\"k\":[1,2,3]," + "\"l\":\"3333-11-22T04:05:06.000Z\"," + "\"m\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}," + "\"TokenExpiresAtTime\":" + "{" + "\"a\":null,\"b\":true,\"c\":false,\"d\":1,\"e\":\"E\",\"f\":{\"x\":true,\"y\":false}," + "\"g\":\"null\",\"h\":\"true\",\"i\":\"false\",\"j\":\"1\",\"k\":[1,2,3]," + "\"l\":\"3333-11-22T04:05:06.000Z\"," + "\"m\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}," + "\"OtherProperty\":" + "{" + "\"a\":null,\"b\":true,\"c\":false,\"d\":1,\"e\":\"E\",\"f\":{\"x\":true,\"y\":false}," + "\"g\":\"null\",\"h\":\"true\",\"i\":\"false\",\"j\":\"1\",\"k\":[1,2,3]," + "\"l\":\"3333-11-22T04:05:06.000Z\"," + "\"m\":\"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}" + "}", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON: Access token property ('TokenForAccessing'): " + "{" + "'a': null, 'b': true, 'c': false, 'd': 1, 'e': string.length=1, 'f': {...}, " + "'g': \"null\", 'h': \"true\", 'i': \"false\", 'j': \"1\", 'k': [...], " + "'l': \"3333-11-22T04:05:06Z\", " + "'m': \"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}, " + "relative expiration property ('TokenExpiresInSeconds'): " + "{" + "'a': null, 'b': true, 'c': false, 'd': 1, 'e': string.length=1, 'f': {...}, " + "'g': \"null\", 'h': \"true\", 'i': \"false\", 'j': \"1\", 'k': [...], " + "'l': \"3333-11-22T04:05:06Z\", " + "'m': \"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}, " + "absolute expiration property ('TokenExpiresAtTime'): " + "{" + "'a': null, 'b': true, 'c': false, 'd': 1, 'e': string.length=1, 'f': {...}, " + "'g': \"null\", 'h': \"true\", 'i': \"false\", 'j': \"1\", 'k': [...], " + "'l': \"3333-11-22T04:05:06Z\", " + "'m': \"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}, " + "other properties: 'OtherProperty': " + "{" + "'a': null, 'b': true, 'c': false, 'd': 1, 'e': string.length=1, 'f': {...}, " + "'g': \"null\", 'h': \"true\", 'i': \"false\", 'j': \"1\", 'k': [...], " + "'l': \"3333-11-22T04:05:06Z\", " + "'m': \"Sun, 22 Nov 3333 04:05:06 GMT\"" + "}."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but a string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"Hello, world!\"", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (string.length=13)."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but a null. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "null", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (null)."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but a boolean true. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "true", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (true)."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but a boolean false. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "false", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (false)."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but a number. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "-1234.56", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (-1234.56)."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an array. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "[1, 2, 3]", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object ([...])."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an "null" string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"nUlL\"", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (\"nUlL\")."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an "true" string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"tRuE\"", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (\"tRuE\")."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an "false" string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"fAlSe\"", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (\"fAlSe\")."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an integer string. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"31337\"", "TokenForAccessing", "TokenExpiresInSeconds", "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (\"31337\")."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an RFC3339 datetime. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"3333-11-22T04:05:06.000Z\"", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (\"3333-11-22T04:05:06Z\")."); + + Logger::SetListener(nullptr); + } + + // Token is not an object, but an RFC1123 datetime. + { + LogMsgVec log; + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + + auto exceptionThrown = false; + try + { + static_cast(TokenCredentialImpl::ParseToken( + "\"Sun, 22 Nov 3333 04:05:06 GMT\"", + "TokenForAccessing", + "TokenExpiresInSeconds", + "TokenExpiresAtTime")); + } + catch (std::exception const& e) + { + exceptionThrown = true; + + EXPECT_EQ( + e.what(), + std::string("Token JSON object: can't find or parse 'TokenForAccessing' property." + "\nSee Azure::Core::Diagnostics::Logger for details" + " (https://aka.ms/azsdk/cpp/identity/troubleshooting).")); + } + + EXPECT_TRUE(exceptionThrown); + EXPECT_EQ(log.size(), 1U); + EXPECT_EQ(log.at(0).first, Logger::Level::Verbose); + EXPECT_EQ( + log.at(0).second, + "Identity: TokenCredentialImpl::ParseToken(): " + "Please report an issue with the following details:\n" + "Token JSON is not an object (\"Sun, 22 Nov 3333 04:05:06 GMT\")."); + + Logger::SetListener(nullptr); + } +}