From 3b231e5850f582cd3183e3f4d0110e1c5a85b05d Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Fri, 9 Feb 2024 16:01:44 -0800 Subject: [PATCH] Enable proactive renewal of Managed Identity tokens. (#5336) * Enable proactive renewal of Managed Identity tokens. * Address PR feedback - move helpers to anonymous namespace and renames. * Address local variable rename suggestion. --- sdk/identity/azure-identity/CHANGELOG.md | 2 + .../src/azure_cli_credential.cpp | 2 + .../src/client_certificate_credential.cpp | 2 +- .../src/client_secret_credential.cpp | 2 +- .../src/managed_identity_source.cpp | 7 +- .../src/private/token_credential_impl.hpp | 21 +- .../src/token_credential_impl.cpp | 93 +++- .../src/workload_identity_credential.cpp | 2 +- .../ut/managed_identity_credential_test.cpp | 135 ++++-- .../test/ut/token_credential_impl_test.cpp | 419 +++++++++++++++++- 10 files changed, 605 insertions(+), 80 deletions(-) diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 88e6777fb..a3b02ba14 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features Added +- [[#4474]](https://github.com/Azure/azure-sdk-for-cpp/issues/4474) Enable proactive renewal of Managed Identity tokens. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/identity/azure-identity/src/azure_cli_credential.cpp b/sdk/identity/azure-identity/src/azure_cli_credential.cpp index 43bb47de6..73c3fd287 100644 --- a/sdk/identity/azure-identity/src/azure_cli_credential.cpp +++ b/sdk/identity/azure-identity/src/azure_cli_credential.cpp @@ -191,6 +191,8 @@ AccessToken AzureCliCredential::GetToken( "accessToken", "expiresIn", std::vector{"expires_on", "expiresOn"}, + "", + false, GetLocalTimeToUtcDiffSeconds()); } catch (json::exception const&) diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp index b257130fd..0aa5bcbac 100644 --- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -503,7 +503,7 @@ AccessToken ClientCertificateCredential::GetToken( // call it later. Therefore, any capture made here will outlive the possible time frame when the // lambda might get called. return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() { - return m_tokenCredentialImpl->GetToken(context, [&]() { + return m_tokenCredentialImpl->GetToken(context, false, [&]() { auto body = m_requestBody; if (!scopesStr.empty()) { diff --git a/sdk/identity/azure-identity/src/client_secret_credential.cpp b/sdk/identity/azure-identity/src/client_secret_credential.cpp index 07f95c751..f7f3fdabb 100644 --- a/sdk/identity/azure-identity/src/client_secret_credential.cpp +++ b/sdk/identity/azure-identity/src/client_secret_credential.cpp @@ -83,7 +83,7 @@ AccessToken ClientSecretCredential::GetToken( // call it later. Therefore, any capture made here will outlive the possible time frame when the // lambda might get called. return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() { - return m_tokenCredentialImpl->GetToken(context, [&]() { + return m_tokenCredentialImpl->GetToken(context, false, [&]() { auto body = m_requestBody; if (!scopesStr.empty()) diff --git a/sdk/identity/azure-identity/src/managed_identity_source.cpp b/sdk/identity/azure-identity/src/managed_identity_source.cpp index dd3a85bd8..4f9290224 100644 --- a/sdk/identity/azure-identity/src/managed_identity_source.cpp +++ b/sdk/identity/azure-identity/src/managed_identity_source.cpp @@ -137,7 +137,7 @@ Azure::Core::Credentials::AccessToken AppServiceManagedIdentitySource::GetToken( // call it later. Therefore, any capture made here will outlive the possible time frame when the // lambda might get called. return m_tokenCache.GetToken(scopesStr, {}, tokenRequestContext.MinimumExpiration, [&]() { - return TokenCredentialImpl::GetToken(context, [&]() { + return TokenCredentialImpl::GetToken(context, true, [&]() { auto request = std::make_unique(m_request); if (!scopesStr.empty()) @@ -219,7 +219,7 @@ Azure::Core::Credentials::AccessToken CloudShellManagedIdentitySource::GetToken( // call it later. Therefore, any capture made here will outlive the possible time frame when the // lambda might get called. return m_tokenCache.GetToken(scopesStr, {}, tokenRequestContext.MinimumExpiration, [&]() { - return TokenCredentialImpl::GetToken(context, [&]() { + return TokenCredentialImpl::GetToken(context, true, [&]() { using Azure::Core::Url; using Azure::Core::Http::HttpMethod; @@ -320,6 +320,7 @@ Azure::Core::Credentials::AccessToken AzureArcManagedIdentitySource::GetToken( return m_tokenCache.GetToken(scopesStr, {}, tokenRequestContext.MinimumExpiration, [&]() { return TokenCredentialImpl::GetToken( context, + true, createRequest, [&](auto const statusCode, auto const& response) -> std::unique_ptr { using Core::Credentials::AuthenticationException; @@ -418,7 +419,7 @@ Azure::Core::Credentials::AccessToken ImdsManagedIdentitySource::GetToken( // call it later. Therefore, any capture made here will outlive the possible time frame when the // lambda might get called. return m_tokenCache.GetToken(scopesStr, {}, tokenRequestContext.MinimumExpiration, [&]() { - return TokenCredentialImpl::GetToken(context, [&]() { + return TokenCredentialImpl::GetToken(context, true, [&]() { auto request = std::make_unique(m_request); if (!scopesStr.empty()) diff --git a/sdk/identity/azure-identity/src/private/token_credential_impl.hpp b/sdk/identity/azure-identity/src/private/token_credential_impl.hpp index b51a1fc05..c4ba6b543 100644 --- a/sdk/identity/azure-identity/src/private/token_credential_impl.hpp +++ b/sdk/identity/azure-identity/src/private/token_credential_impl.hpp @@ -70,6 +70,10 @@ namespace Azure { namespace Identity { namespace _detail { * @param expiresOnPropertyNames Names of properties in the JSON object that represent token * expiration as absolute date-time stamp. Can be empty, in which case no attempt to parse the * corresponding property will be made. Empty string elements will be ignored. + * @param refreshInPropertyName Name of a property in the JSON object that represents when to + * refresh the token in number of seconds from now. + * @param proactiveRenewal A value to indicate whether to refresh tokens, proactively, with half + * lifetime or not. * @param utcDiffSeconds Optional. If not 0, it represents the difference between the UTC and a * desired time zone, in seconds. Then, should an RFC3339 timestamp come without a time zone * information, a corresponding time zone offset will be applied to such timestamp. @@ -88,6 +92,8 @@ namespace Azure { namespace Identity { namespace _detail { std::string const& accessTokenPropertyName, std::string const& expiresInPropertyName, std::vector const& expiresOnPropertyNames, + std::string const& refreshInPropertyName = "", + bool proactiveRenewal = false, int utcDiffSeconds = 0); /** @@ -101,6 +107,10 @@ namespace Azure { namespace Identity { namespace _detail { * @param expiresOnPropertyName Name of a property in the JSON object that represents token * expiration as absolute date-time stamp. Can be empty, in which case no attempt to parse it is * made. + * @param refreshInPropertyName Name of a property in the JSON object that represents + * when to refresh the token in number of seconds from now. + * @param proactiveRenewal A value to indicate whether to refresh tokens, proactively, with half + * lifetime or not. * * @return A successfully parsed access token. * @@ -110,13 +120,17 @@ namespace Azure { namespace Identity { namespace _detail { std::string const& jsonString, std::string const& accessTokenPropertyName, std::string const& expiresInPropertyName, - std::string const& expiresOnPropertyName) + std::string const& expiresOnPropertyName, + std::string const& refreshInPropertyName = "", + bool proactiveRenewal = false) { return ParseToken( jsonString, accessTokenPropertyName, expiresInPropertyName, - std::vector{expiresOnPropertyName}); + std::vector{expiresOnPropertyName}, + refreshInPropertyName, + proactiveRenewal); } /** @@ -169,6 +183,8 @@ namespace Azure { namespace Identity { namespace _detail { * @brief Gets an authentication token. * * @param context A context to control the request lifetime. + * @param proactiveRenewal A value to indicate whether to refresh tokens, proactively, with half + * lifetime or not. * @param createRequest A function to create a token request. * @param shouldRetry A function to determine whether a response should be retried with * another request. @@ -177,6 +193,7 @@ namespace Azure { namespace Identity { namespace _detail { */ Core::Credentials::AccessToken GetToken( Core::Context const& context, + bool proactiveRenewal, std::function()> const& createRequest, std::function( Core::Http::HttpStatusCode statusCode, diff --git a/sdk/identity/azure-identity/src/token_credential_impl.cpp b/sdk/identity/azure-identity/src/token_credential_impl.cpp index dc15dafaf..7af014156 100644 --- a/sdk/identity/azure-identity/src/token_credential_impl.cpp +++ b/sdk/identity/azure-identity/src/token_credential_impl.cpp @@ -35,6 +35,8 @@ using Azure::Core::Http::HttpStatusCode; using Azure::Core::Http::RawResponse; using Azure::Core::Json::_internal::json; +using namespace std::chrono_literals; + TokenCredentialImpl::TokenCredentialImpl(TokenCredentialOptions const& options) : m_httpPipeline(options, "identity", PackageVersion::ToString(), {}, {}) { @@ -91,6 +93,7 @@ std::string TokenCredentialImpl::FormatScopes( AccessToken TokenCredentialImpl::GetToken( Context const& context, + bool proactiveRenewal, std::function()> const& createRequest, std::function( HttpStatusCode statusCode, @@ -140,7 +143,9 @@ AccessToken TokenCredentialImpl::GetToken( std::string(responseBodyVector.begin(), responseBodyVector.end()), "access_token", "expires_in", - "expires_on"); + "expires_on", + "refresh_in", + proactiveRenewal); } catch (AuthenticationException const&) { @@ -224,6 +229,30 @@ std::string TimeZoneOffsetAsString(int utcDiffSeconds) return os.str(); } +// Proactive renewal by cutting the refresh time in half if the token expires in more than +// 2 hours. +std::chrono::seconds GetProactiveRenewalSeconds(std::chrono::seconds seconds) +{ + if (seconds >= std::chrono::seconds(2h)) + { + return seconds / 2; + } + else + { + return seconds; + } +} + +DateTime GetProactiveRenewalDateTime(std::int64_t posixTimestamp) +{ + const DateTime now = DateTime::clock::now(); + + const auto renewInSeconds = std::chrono::duration_cast( + PosixTimeConverter::PosixTimeToDateTime(posixTimestamp) - now); + + return DateTime(now + GetProactiveRenewalSeconds(renewInSeconds)); +} + } // namespace AccessToken TokenCredentialImpl::ParseToken( @@ -231,6 +260,8 @@ AccessToken TokenCredentialImpl::ParseToken( std::string const& accessTokenPropertyName, std::string const& expiresInPropertyName, std::vector const& expiresOnPropertyNames, + std::string const& refreshInPropertyName, + bool proactiveRenewal, int utcDiffSeconds) { json parsedJson; @@ -262,6 +293,35 @@ AccessToken TokenCredentialImpl::ParseToken( accessToken.Token = parsedJson[accessTokenPropertyName].get(); accessToken.ExpiresOn = std::chrono::system_clock::now(); + // expiresIn = number of seconds until refresh. + // expiresOn = timestamp of refresh expressed as seconds since epoch. + + if (!refreshInPropertyName.empty() && parsedJson.contains(refreshInPropertyName)) + { + auto const& refreshIn = parsedJson[refreshInPropertyName]; + if (refreshIn.is_number_unsigned()) + { + try + { + // 'refresh_in' as number (seconds until refresh) + auto const value = refreshIn.get(); + if (value <= MaxExpirationInSeconds) + { + static_assert( + MaxExpirationInSeconds <= std::numeric_limits::max(), + "Can safely cast to int32"); + + accessToken.ExpiresOn += std::chrono::seconds(static_cast(value)); + return accessToken; + } + } + catch (std::exception const&) + { + // refreshIn.get() has thrown, we may throw later. + } + } + } + if (parsedJson.contains(expiresInPropertyName)) { auto const& expiresIn = parsedJson[expiresInPropertyName]; @@ -278,7 +338,9 @@ AccessToken TokenCredentialImpl::ParseToken( MaxExpirationInSeconds <= std::numeric_limits::max(), "Can safely cast to int32"); - accessToken.ExpiresOn += std::chrono::seconds(static_cast(value)); + auto expiresInSeconds = std::chrono::seconds(static_cast(value)); + accessToken.ExpiresOn + += proactiveRenewal ? GetProactiveRenewalSeconds(expiresInSeconds) : expiresInSeconds; return accessToken; } } @@ -297,8 +359,10 @@ AccessToken TokenCredentialImpl::ParseToken( MaxExpirationInSeconds <= std::numeric_limits::max(), "Can safely cast to int32"); - accessToken.ExpiresOn += std::chrono::seconds(static_cast( + auto expiresInSeconds = std::chrono::seconds(static_cast( ParseNumericExpiration(expiresIn.get(), MaxExpirationInSeconds))); + accessToken.ExpiresOn + += proactiveRenewal ? GetProactiveRenewalSeconds(expiresInSeconds) : expiresInSeconds; return accessToken; } @@ -342,7 +406,9 @@ AccessToken TokenCredentialImpl::ParseToken( auto const value = expiresOn.get(); if (value <= MaxPosixTimestamp) { - accessToken.ExpiresOn = PosixTimeConverter::PosixTimeToDateTime(value); + accessToken.ExpiresOn = proactiveRenewal + ? GetProactiveRenewalDateTime(value) + : PosixTimeConverter::PosixTimeToDateTime(value); return accessToken; } } @@ -359,16 +425,23 @@ AccessToken TokenCredentialImpl::ParseToken( for (auto const& parse : { std::function([&](auto const& s) { // 'expires_on' as RFC3339 date string (absolute timestamp) - return DateTime::Parse(s + tzOffsetStr, DateTime::DateFormat::Rfc3339); + auto dateTime = DateTime::Parse(s + tzOffsetStr, DateTime::DateFormat::Rfc3339); + return proactiveRenewal ? GetProactiveRenewalDateTime( + PosixTimeConverter::DateTimeToPosixTime(dateTime)) + : dateTime; }), - std::function([](auto const& s) { + std::function([&](auto const& s) { // 'expires_on' as numeric string (posix time representing an absolute timestamp) - return PosixTimeConverter::PosixTimeToDateTime( - ParseNumericExpiration(s, MaxPosixTimestamp)); + auto value = ParseNumericExpiration(s, MaxPosixTimestamp); + return proactiveRenewal ? GetProactiveRenewalDateTime(value) + : PosixTimeConverter::PosixTimeToDateTime(value); }), - std::function([](auto const& s) { + std::function([&](auto const& s) { // 'expires_on' as RFC1123 date string (absolute timestamp) - return DateTime::Parse(s, DateTime::DateFormat::Rfc1123); + auto dateTime = DateTime::Parse(s, DateTime::DateFormat::Rfc1123); + return proactiveRenewal ? GetProactiveRenewalDateTime( + PosixTimeConverter::DateTimeToPosixTime(dateTime)) + : dateTime; }), }) { diff --git a/sdk/identity/azure-identity/src/workload_identity_credential.cpp b/sdk/identity/azure-identity/src/workload_identity_credential.cpp index 19607ef12..2fe0cd213 100644 --- a/sdk/identity/azure-identity/src/workload_identity_credential.cpp +++ b/sdk/identity/azure-identity/src/workload_identity_credential.cpp @@ -129,7 +129,7 @@ AccessToken WorkloadIdentityCredential::GetToken( // call it later. Therefore, any capture made here will outlive the possible time frame when the // lambda might get called. return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() { - return m_tokenCredentialImpl->GetToken(context, [&]() { + return m_tokenCredentialImpl->GetToken(context, false, [&]() { auto body = m_requestBody; if (!scopesStr.empty()) { diff --git a/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp b/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp index 92abeb936..2f7f44636 100644 --- a/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp @@ -125,11 +125,11 @@ TEST(ManagedIdentityCredential, AppServiceV2019) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); Logger::SetListener(nullptr); } @@ -217,11 +217,11 @@ TEST(ManagedIdentityCredential, AppServiceV2019ClientId) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); } TEST(ManagedIdentityCredential, AppServiceV2019InvalidUrl) @@ -407,11 +407,11 @@ TEST(ManagedIdentityCredential, AppServiceV2017) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); Logger::SetListener(nullptr); } @@ -499,11 +499,11 @@ TEST(ManagedIdentityCredential, AppServiceV2017ClientId) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); } TEST(ManagedIdentityCredential, AppServiceV2017InvalidUrl) @@ -665,11 +665,11 @@ TEST(ManagedIdentityCredential, CloudShell) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); Logger::SetListener(nullptr); } @@ -747,11 +747,11 @@ TEST(ManagedIdentityCredential, CloudShellClientId) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); } TEST(ManagedIdentityCredential, CloudShellInvalidUrl) @@ -965,11 +965,11 @@ TEST(ManagedIdentityCredential, AzureArc) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); Logger::SetListener(nullptr); } @@ -1234,26 +1234,38 @@ TEST(ManagedIdentityCredential, Imds) return credential; }, - {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, + {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}, {}, {}, {}}, std::vector{ "{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}", "{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}", - "{\"expires_in\":9999, \"access_token\":\"ACCESSTOKEN3\"}"}); + "{\"expires_in\":9999, \"access_token\":\"ACCESSTOKEN3\"}", + "{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN4\", \"refresh_in\":9999}", + "{\"expires_in\":7199, \"access_token\":\"ACCESSTOKEN5\"}", + "{\"expires_in\":7202, \"access_token\":\"ACCESSTOKEN6\"}"}); - EXPECT_EQ(actual.Requests.size(), 3U); - EXPECT_EQ(actual.Responses.size(), 3U); + EXPECT_EQ(actual.Requests.size(), 6U); + EXPECT_EQ(actual.Responses.size(), 6U); auto const& request0 = actual.Requests.at(0); auto const& request1 = actual.Requests.at(1); auto const& request2 = actual.Requests.at(2); + auto const& request3 = actual.Requests.at(3); + auto const& request4 = actual.Requests.at(4); + auto const& request5 = actual.Requests.at(5); auto const& response0 = actual.Responses.at(0); auto const& response1 = actual.Responses.at(1); auto const& response2 = actual.Responses.at(2); + auto const& response3 = actual.Responses.at(3); + auto const& response4 = actual.Responses.at(4); + auto const& response5 = actual.Responses.at(5); EXPECT_EQ(request0.HttpMethod, HttpMethod::Get); EXPECT_EQ(request1.HttpMethod, HttpMethod::Get); EXPECT_EQ(request2.HttpMethod, HttpMethod::Get); + EXPECT_EQ(request3.HttpMethod, HttpMethod::Get); + EXPECT_EQ(request4.HttpMethod, HttpMethod::Get); + EXPECT_EQ(request5.HttpMethod, HttpMethod::Get); EXPECT_EQ( request0.AbsoluteUrl, @@ -1272,9 +1284,27 @@ TEST(ManagedIdentityCredential, Imds) "http://169.254.169.254/metadata/identity/oauth2/token" "?api-version=2018-02-01"); + EXPECT_EQ( + request3.AbsoluteUrl, + "http://169.254.169.254/metadata/identity/oauth2/token" + "?api-version=2018-02-01"); + + EXPECT_EQ( + request4.AbsoluteUrl, + "http://169.254.169.254/metadata/identity/oauth2/token" + "?api-version=2018-02-01"); + + EXPECT_EQ( + request5.AbsoluteUrl, + "http://169.254.169.254/metadata/identity/oauth2/token" + "?api-version=2018-02-01"); + EXPECT_TRUE(request0.Body.empty()); EXPECT_TRUE(request1.Body.empty()); EXPECT_TRUE(request2.Body.empty()); + EXPECT_TRUE(request3.Body.empty()); + EXPECT_TRUE(request4.Body.empty()); + EXPECT_TRUE(request5.Body.empty()); { EXPECT_NE(request0.Headers.find("Metadata"), request0.Headers.end()); @@ -1285,21 +1315,42 @@ TEST(ManagedIdentityCredential, Imds) EXPECT_NE(request2.Headers.find("Metadata"), request2.Headers.end()); EXPECT_EQ(request2.Headers.at("Metadata"), "true"); + + EXPECT_NE(request3.Headers.find("Metadata"), request3.Headers.end()); + EXPECT_EQ(request3.Headers.at("Metadata"), "true"); + + EXPECT_NE(request4.Headers.find("Metadata"), request4.Headers.end()); + EXPECT_EQ(request4.Headers.at("Metadata"), "true"); + + EXPECT_NE(request5.Headers.find("Metadata"), request5.Headers.end()); + EXPECT_EQ(request5.Headers.at("Metadata"), "true"); } EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1"); EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2"); EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3"); + EXPECT_EQ(response3.AccessToken.Token, "ACCESSTOKEN4"); + EXPECT_EQ(response4.AccessToken.Token, "ACCESSTOKEN5"); + EXPECT_EQ(response5.AccessToken.Token, "ACCESSTOKEN6"); using namespace std::chrono_literals; EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); + + EXPECT_GE(response3.AccessToken.ExpiresOn, response3.EarliestExpiration + 9999s); + EXPECT_LE(response3.AccessToken.ExpiresOn, response3.LatestExpiration + 9999s); + + EXPECT_GE(response4.AccessToken.ExpiresOn, response4.EarliestExpiration + 7199s); + EXPECT_LE(response4.AccessToken.ExpiresOn, response4.LatestExpiration + 7199s); + + EXPECT_GE(response5.AccessToken.ExpiresOn, response5.EarliestExpiration + 3601s); + EXPECT_LE(response5.AccessToken.ExpiresOn, response5.LatestExpiration + 3601s); Logger::SetListener(nullptr); } @@ -1387,11 +1438,11 @@ TEST(ManagedIdentityCredential, ImdsClientId) EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); - EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); - EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); } TEST(ManagedIdentityCredential, ImdsCreation) @@ -1483,6 +1534,6 @@ TEST(ManagedIdentityCredential, ImdsCreation) EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s); EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s); - EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 7200s); - EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 7200s); + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 3600s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 3600s); } 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 d6d7b3ace..2d28e1303 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 @@ -31,30 +31,35 @@ private: HttpMethod m_httpMethod = HttpMethod(std::string()); Url m_url; std::unique_ptr m_tokenCredentialImpl; + bool m_proactiveRenewal; public: explicit TokenCredentialImplTester( HttpMethod httpMethod, Url url, - TokenCredentialOptions const& options) + TokenCredentialOptions const& options, + bool proactiveRenewal = false) : TokenCredential("TokenCredentialImplTester"), m_httpMethod(std::move(httpMethod)), - m_url(std::move(url)), m_tokenCredentialImpl(new TokenCredentialImpl(options)) + m_url(std::move(url)), m_tokenCredentialImpl(new TokenCredentialImpl(options)), + m_proactiveRenewal(proactiveRenewal) { } explicit TokenCredentialImplTester( std::function throwingFunction, - TokenCredentialOptions const& options) + TokenCredentialOptions const& options, + bool proactiveRenewal = false) : TokenCredential("TokenCredentialImplTester"), m_throwingFunction(std::move(throwingFunction)), - m_tokenCredentialImpl(new TokenCredentialImpl(options)) + m_tokenCredentialImpl(new TokenCredentialImpl(options)), + m_proactiveRenewal(proactiveRenewal) { } AccessToken GetToken(TokenRequestContext const& tokenRequestContext, Context const& context) const override { - return m_tokenCredentialImpl->GetToken(context, [&]() { + return m_tokenCredentialImpl->GetToken(context, m_proactiveRenewal, [&]() { m_throwingFunction(); std::string scopesStr; @@ -179,6 +184,95 @@ TEST(TokenCredentialImpl, Normal) EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); } +TEST(TokenCredentialImpl, Normal_ProactiveRenewal) +{ + auto const actual = CredentialTestHelper::SimulateTokenRequest( + [](auto transport) { + TokenCredentialOptions options; + options.Transport.Transport = transport; + + return std::make_unique( + HttpMethod::Delete, Url("https://outlook.com/"), options, true); + }, + {{"https://azure.com/.default", "https://microsoft.com/.default"}, + {"https://azure.com/.default", "https://microsoft.com/.default"}, + {"https://azure.com/.default", "https://microsoft.com/.default"}, + {"https://azure.com/.default", "https://microsoft.com/.default"}}, + std::vector{ + "{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}", + "{\"access_token\":\"ACCESSTOKEN2\", \"expires_in\":7199}", + "{\"access_token\":\"ACCESSTOKEN3\", \"expires_in\":7200}", + "{\"ab\":1,\"expires_in\":9999,\"cd\":2,\"access_token\":\"ACCESSTOKEN4\",\"ef\":3}"}); + + EXPECT_EQ(actual.Responses.size(), 4U); + + auto const& response0 = actual.Responses.at(0); + auto const& response1 = actual.Responses.at(1); + auto const& response2 = actual.Responses.at(2); + auto const& response3 = actual.Responses.at(3); + + EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1"); + EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2"); + EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3"); + EXPECT_EQ(response3.AccessToken.Token, "ACCESSTOKEN4"); + + using namespace std::chrono_literals; + EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); + EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); + + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7199s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7199s); + + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 3600s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 3600s); + + EXPECT_GE(response3.AccessToken.ExpiresOn, response3.EarliestExpiration + 4999s); + EXPECT_LE(response3.AccessToken.ExpiresOn, response3.LatestExpiration + 4999s); +} + +TEST(TokenCredentialImpl, RefreshInTakesPrecedence) +{ + for (bool proactiveRenewal : {false, true}) + { + auto const actual = CredentialTestHelper::SimulateTokenRequest( + [&](auto transport) { + TokenCredentialOptions options; + options.Transport.Transport = transport; + + return std::make_unique( + HttpMethod::Delete, Url("https://outlook.com/"), options, proactiveRenewal); + }, + {{"https://azure.com/.default", "https://microsoft.com/.default"}, + {"https://azure.com/.default", "https://microsoft.com/.default"}, + {"https://azure.com/.default", "https://microsoft.com/.default"}}, + std::vector{ + "{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN1\", \"refresh_in\":8400}", + "{\"access_token\":\"ACCESSTOKEN2\", \"refresh_in\":1200}", + "{\"refresh_in\":9998, " + "\"ab\":1,\"expires_in\":9999,\"cd\":2,\"access_token\":\"ACCESSTOKEN3\",\"ef\":3}"}); + + EXPECT_EQ(actual.Responses.size(), 3U); + + auto const& response0 = actual.Responses.at(0); + auto const& response1 = actual.Responses.at(1); + auto const& response2 = actual.Responses.at(2); + + EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1"); + EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2"); + EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3"); + + using namespace std::chrono_literals; + EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 8400s); + EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 8400s); + + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 1200s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 1200s); + + EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9998s); + EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9998s); + } +} + TEST(TokenCredentialImpl, StdException) { static_cast(CredentialTestHelper::SimulateTokenRequest( @@ -334,22 +428,25 @@ TEST(TokenCredentialImpl, FormatScopes) TEST(TokenCredentialImpl, NoExpiration) { - static_cast(CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { - TokenCredentialOptions options; - options.Transport.Transport = transport; + for (bool proactiveRenewal : {false, true}) + { + static_cast(CredentialTestHelper::SimulateTokenRequest( + [&](auto transport) { + TokenCredentialOptions options; + options.Transport.Transport = transport; - return std::make_unique( - HttpMethod::Delete, Url("https://outlook.com/"), options); - }, - {{"https://azure.com/.default", "https://microsoft.com/.default"}}, - {"{\"access_token\":\"ACCESSTOKEN\"}"}, - [](auto& credential, auto& tokenRequestContext, auto& context) { - AccessToken token; - EXPECT_THROW( - token = credential.GetToken(tokenRequestContext, context), AuthenticationException); - return token; - })); + return std::make_unique( + HttpMethod::Delete, Url("https://outlook.com/"), options, proactiveRenewal); + }, + {{"https://azure.com/.default", "https://microsoft.com/.default"}}, + {"{\"access_token\":\"ACCESSTOKEN\"}"}, + [](auto& credential, auto& tokenRequestContext, auto& context) { + AccessToken token; + EXPECT_THROW( + token = credential.GetToken(tokenRequestContext, context), AuthenticationException); + return token; + })); + } } TEST(TokenCredentialImpl, NoToken) @@ -667,6 +764,268 @@ TEST(TokenCredentialImpl, ExpirationFormats) EXPECT_EQ(response43.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); } +TEST(TokenCredentialImpl, ExpirationFormats_ProactiveRenewal) +{ + auto const actual = CredentialTestHelper::SimulateTokenRequest( + [](auto transport) { + TokenCredentialOptions options; + options.Transport.Transport = transport; + + return std::make_unique( + HttpMethod::Get, Url("https://microsoft.com/"), options, true); + }, + std::vector(47, {"https://azure.com/.default"}), + std::vector{ + MakeTokenResponse("00", "3600", ""), + MakeTokenResponse("01", "\"3600\"", ""), + MakeTokenResponse("02", "\"unknown format\"", ""), + MakeTokenResponse("03", "\"\"", ""), + MakeTokenResponse("04", "null", ""), + MakeTokenResponse("05", "", "43040261106"), + MakeTokenResponse("06", "", "\"43040261106\""), + MakeTokenResponse("07", "", "\"3333-11-22T04:05:06.000Z\""), + MakeTokenResponse("08", "", "\"Sun, 22 Nov 3333 04:05:06 GMT\""), + MakeTokenResponse("09", "", "\"unknown format\""), + MakeTokenResponse("10", "", "\"\""), + MakeTokenResponse("11", "", "null"), + MakeTokenResponse("12", "3600", "43040261106"), + MakeTokenResponse("13", "3600", "\"43040261106\""), + MakeTokenResponse("14", "3600", "\"3333-11-22T04:05:06.000Z\""), + MakeTokenResponse("15", "3600", "\"Sun, 22 Nov 3333 04:05:06 GMT\""), + MakeTokenResponse("16", "3600", "\"unknown format\""), + MakeTokenResponse("17", "3600", "\"\""), + MakeTokenResponse("18", "3600", "null"), + MakeTokenResponse("19", "\"3600\"", "43040261106"), + MakeTokenResponse("20", "\"3600\"", "\"43040261106\""), + MakeTokenResponse("21", "\"3600\"", "\"3333-11-22T04:05:06.000Z\""), + MakeTokenResponse("22", "\"3600\"", "\"Sun, 22 Nov 3333 04:05:06 GMT\""), + MakeTokenResponse("23", "\"3600\"", "\"unknown format\""), + MakeTokenResponse("24", "\"3600\"", "\"\""), + MakeTokenResponse("25", "\"3600\"", "null"), + MakeTokenResponse("26", "\"unknown format\"", "43040261106"), + MakeTokenResponse("27", "\"unknown format\"", "\"43040261106\""), + MakeTokenResponse("28", "\"unknown format\"", "\"3333-11-22T04:05:06.000Z\""), + MakeTokenResponse("29", "\"unknown format\"", "\"Sun, 22 Nov 3333 04:05:06 GMT\""), + MakeTokenResponse("30", "\"unknown format\"", "\"unknown format\""), + MakeTokenResponse("31", "\"unknown format\"", "\"\""), + MakeTokenResponse("32", "\"unknown format\"", "null"), + MakeTokenResponse("33", "\"\"", "43040261106"), + MakeTokenResponse("34", "\"\"", "\"43040261106\""), + MakeTokenResponse("35", "\"\"", "\"3333-11-22T04:05:06.000Z\""), + MakeTokenResponse("36", "\"\"", "\"Sun, 22 Nov 3333 04:05:06 GMT\""), + MakeTokenResponse("37", "\"\"", "\"unknown format\""), + MakeTokenResponse("38", "\"\"", "\"\""), + MakeTokenResponse("39", "\"\"", "null"), + MakeTokenResponse("40", "null", "43040261106"), + MakeTokenResponse("41", "null", "\"43040261106\""), + MakeTokenResponse("42", "null", "\"3333-11-22T04:05:06.000Z\""), + MakeTokenResponse("43", "null", "\"Sun, 22 Nov 3333 04:05:06 GMT\""), + MakeTokenResponse("44", "null", "\"unknown format\""), + MakeTokenResponse("45", "null", "\"\""), + MakeTokenResponse("46", "null", "null"), + }, + [](auto& credential, auto& tokenRequestContext, auto& context) { + AccessToken token; + token.Token = "FAILED"; + + try + { + token = credential.GetToken(tokenRequestContext, context); + } + catch (AuthenticationException const&) + { + } + + return token; + }); + + EXPECT_EQ(actual.Requests.size(), 47U); + EXPECT_EQ(actual.Responses.size(), 47U); + + auto const& response00 = actual.Responses.at(0); + auto const& response01 = actual.Responses.at(1); + auto const& response02 = actual.Responses.at(2); + auto const& response03 = actual.Responses.at(3); + auto const& response04 = actual.Responses.at(4); + auto const& response05 = actual.Responses.at(5); + auto const& response06 = actual.Responses.at(6); + auto const& response07 = actual.Responses.at(7); + auto const& response08 = actual.Responses.at(8); + auto const& response09 = actual.Responses.at(9); + auto const& response10 = actual.Responses.at(10); + auto const& response11 = actual.Responses.at(11); + auto const& response12 = actual.Responses.at(12); + auto const& response13 = actual.Responses.at(13); + auto const& response14 = actual.Responses.at(14); + auto const& response15 = actual.Responses.at(15); + auto const& response16 = actual.Responses.at(16); + auto const& response17 = actual.Responses.at(17); + auto const& response18 = actual.Responses.at(18); + auto const& response19 = actual.Responses.at(19); + auto const& response20 = actual.Responses.at(20); + auto const& response21 = actual.Responses.at(21); + auto const& response22 = actual.Responses.at(22); + auto const& response23 = actual.Responses.at(23); + auto const& response24 = actual.Responses.at(24); + auto const& response25 = actual.Responses.at(25); + auto const& response26 = actual.Responses.at(26); + auto const& response27 = actual.Responses.at(27); + auto const& response28 = actual.Responses.at(28); + auto const& response29 = actual.Responses.at(29); + auto const& response30 = actual.Responses.at(30); + auto const& response31 = actual.Responses.at(31); + auto const& response32 = actual.Responses.at(32); + auto const& response33 = actual.Responses.at(33); + auto const& response34 = actual.Responses.at(34); + auto const& response35 = actual.Responses.at(35); + auto const& response36 = actual.Responses.at(36); + auto const& response37 = actual.Responses.at(37); + auto const& response38 = actual.Responses.at(38); + auto const& response39 = actual.Responses.at(39); + auto const& response40 = actual.Responses.at(40); + auto const& response41 = actual.Responses.at(41); + auto const& response42 = actual.Responses.at(42); + auto const& response43 = actual.Responses.at(43); + auto const& response44 = actual.Responses.at(44); + auto const& response45 = actual.Responses.at(45); + auto const& response46 = actual.Responses.at(46); + + EXPECT_EQ(response00.AccessToken.Token, "ACCESSTOKEN00"); + EXPECT_EQ(response01.AccessToken.Token, "ACCESSTOKEN01"); + EXPECT_EQ(response02.AccessToken.Token, "FAILED"); + EXPECT_EQ(response03.AccessToken.Token, "FAILED"); + EXPECT_EQ(response04.AccessToken.Token, "FAILED"); + EXPECT_EQ(response05.AccessToken.Token, "ACCESSTOKEN05"); + EXPECT_EQ(response06.AccessToken.Token, "ACCESSTOKEN06"); + EXPECT_EQ(response07.AccessToken.Token, "ACCESSTOKEN07"); + EXPECT_EQ(response08.AccessToken.Token, "ACCESSTOKEN08"); + EXPECT_EQ(response09.AccessToken.Token, "FAILED"); + EXPECT_EQ(response10.AccessToken.Token, "FAILED"); + EXPECT_EQ(response11.AccessToken.Token, "FAILED"); + EXPECT_EQ(response12.AccessToken.Token, "ACCESSTOKEN12"); + EXPECT_EQ(response13.AccessToken.Token, "ACCESSTOKEN13"); + EXPECT_EQ(response14.AccessToken.Token, "ACCESSTOKEN14"); + EXPECT_EQ(response15.AccessToken.Token, "ACCESSTOKEN15"); + EXPECT_EQ(response16.AccessToken.Token, "ACCESSTOKEN16"); + EXPECT_EQ(response17.AccessToken.Token, "ACCESSTOKEN17"); + EXPECT_EQ(response18.AccessToken.Token, "ACCESSTOKEN18"); + EXPECT_EQ(response19.AccessToken.Token, "ACCESSTOKEN19"); + EXPECT_EQ(response20.AccessToken.Token, "ACCESSTOKEN20"); + EXPECT_EQ(response21.AccessToken.Token, "ACCESSTOKEN21"); + EXPECT_EQ(response22.AccessToken.Token, "ACCESSTOKEN22"); + EXPECT_EQ(response23.AccessToken.Token, "ACCESSTOKEN23"); + EXPECT_EQ(response24.AccessToken.Token, "ACCESSTOKEN24"); + EXPECT_EQ(response25.AccessToken.Token, "ACCESSTOKEN25"); + EXPECT_EQ(response26.AccessToken.Token, "ACCESSTOKEN26"); + EXPECT_EQ(response27.AccessToken.Token, "ACCESSTOKEN27"); + EXPECT_EQ(response28.AccessToken.Token, "ACCESSTOKEN28"); + EXPECT_EQ(response29.AccessToken.Token, "ACCESSTOKEN29"); + EXPECT_EQ(response30.AccessToken.Token, "FAILED"); + EXPECT_EQ(response31.AccessToken.Token, "FAILED"); + EXPECT_EQ(response32.AccessToken.Token, "FAILED"); + EXPECT_EQ(response33.AccessToken.Token, "ACCESSTOKEN33"); + EXPECT_EQ(response34.AccessToken.Token, "ACCESSTOKEN34"); + EXPECT_EQ(response35.AccessToken.Token, "ACCESSTOKEN35"); + EXPECT_EQ(response36.AccessToken.Token, "ACCESSTOKEN36"); + EXPECT_EQ(response37.AccessToken.Token, "FAILED"); + EXPECT_EQ(response38.AccessToken.Token, "FAILED"); + EXPECT_EQ(response39.AccessToken.Token, "FAILED"); + EXPECT_EQ(response40.AccessToken.Token, "ACCESSTOKEN40"); + EXPECT_EQ(response41.AccessToken.Token, "ACCESSTOKEN41"); + EXPECT_EQ(response42.AccessToken.Token, "ACCESSTOKEN42"); + EXPECT_EQ(response43.AccessToken.Token, "ACCESSTOKEN43"); + EXPECT_EQ(response44.AccessToken.Token, "FAILED"); + EXPECT_EQ(response45.AccessToken.Token, "FAILED"); + EXPECT_EQ(response46.AccessToken.Token, "FAILED"); + + using namespace std::chrono_literals; + EXPECT_GE(response00.AccessToken.ExpiresOn, response00.EarliestExpiration + 3600s); + EXPECT_LE(response00.AccessToken.ExpiresOn, response00.LatestExpiration + 3600s); + + EXPECT_GE(response01.AccessToken.ExpiresOn, response01.EarliestExpiration + 3600s); + EXPECT_LE(response01.AccessToken.ExpiresOn, response01.LatestExpiration + 3600s); + + // Approximate midpoint between now and the actual expiry date, simulating half lifetime of the + // expiry. + EXPECT_GE(response05.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response05.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response06.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response06.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response07.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response07.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response08.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response08.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + + EXPECT_GE(response12.AccessToken.ExpiresOn, response12.EarliestExpiration + 3600s); + EXPECT_LE(response12.AccessToken.ExpiresOn, response12.LatestExpiration + 3600s); + + EXPECT_GE(response13.AccessToken.ExpiresOn, response13.EarliestExpiration + 3600s); + EXPECT_LE(response13.AccessToken.ExpiresOn, response13.LatestExpiration + 3600s); + + EXPECT_GE(response14.AccessToken.ExpiresOn, response14.EarliestExpiration + 3600s); + EXPECT_LE(response14.AccessToken.ExpiresOn, response14.LatestExpiration + 3600s); + + EXPECT_GE(response15.AccessToken.ExpiresOn, response15.EarliestExpiration + 3600s); + EXPECT_LE(response15.AccessToken.ExpiresOn, response15.LatestExpiration + 3600s); + + EXPECT_GE(response16.AccessToken.ExpiresOn, response16.EarliestExpiration + 3600s); + EXPECT_LE(response16.AccessToken.ExpiresOn, response16.LatestExpiration + 3600s); + + EXPECT_GE(response17.AccessToken.ExpiresOn, response17.EarliestExpiration + 3600s); + EXPECT_LE(response17.AccessToken.ExpiresOn, response17.LatestExpiration + 3600s); + + EXPECT_GE(response18.AccessToken.ExpiresOn, response18.EarliestExpiration + 3600s); + EXPECT_LE(response18.AccessToken.ExpiresOn, response18.LatestExpiration + 3600s); + + EXPECT_GE(response19.AccessToken.ExpiresOn, response19.EarliestExpiration + 3600s); + EXPECT_LE(response19.AccessToken.ExpiresOn, response19.LatestExpiration + 3600s); + + EXPECT_GE(response20.AccessToken.ExpiresOn, response20.EarliestExpiration + 3600s); + EXPECT_LE(response20.AccessToken.ExpiresOn, response20.LatestExpiration + 3600s); + + EXPECT_GE(response21.AccessToken.ExpiresOn, response21.EarliestExpiration + 3600s); + EXPECT_LE(response21.AccessToken.ExpiresOn, response21.LatestExpiration + 3600s); + + EXPECT_GE(response22.AccessToken.ExpiresOn, response22.EarliestExpiration + 3600s); + EXPECT_LE(response22.AccessToken.ExpiresOn, response22.LatestExpiration + 3600s); + + EXPECT_GE(response23.AccessToken.ExpiresOn, response23.EarliestExpiration + 3600s); + EXPECT_LE(response23.AccessToken.ExpiresOn, response23.LatestExpiration + 3600s); + + EXPECT_GE(response24.AccessToken.ExpiresOn, response24.EarliestExpiration + 3600s); + EXPECT_LE(response24.AccessToken.ExpiresOn, response24.LatestExpiration + 3600s); + + EXPECT_GE(response25.AccessToken.ExpiresOn, response25.EarliestExpiration + 3600s); + EXPECT_LE(response25.AccessToken.ExpiresOn, response25.LatestExpiration + 3600s); + + EXPECT_GE(response26.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response26.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response27.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response27.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response28.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response28.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response29.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response29.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + + EXPECT_GE(response33.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response33.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response34.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response34.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response35.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response35.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response36.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response36.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + + EXPECT_GE(response40.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response40.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response41.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response41.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response42.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response42.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); + EXPECT_GE(response43.AccessToken.ExpiresOn, DateTime(2678, 12, 31, 16, 19, 11)); + EXPECT_LE(response43.AccessToken.ExpiresOn, DateTime(3333, 11, 22, 4, 5, 6)); +} + TEST(TokenCredentialImpl, MaxValues) { // 'exp_in' negative @@ -689,6 +1048,26 @@ TEST(TokenCredentialImpl, MaxValues) "{\"token\": \"x\",\"exp_in\":2147483648}", "token", "exp_in", "exp_at")), std::exception); + // 'ref_in' negative + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"ref_in\":-1}", "token", "exp_in", "exp_at", "ref_in")), + std::exception); + + // 'ref_in' zero + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"ref_in\":0}", "token", "exp_in", "exp_at", "ref_in"))); + + // 'ref_in' == int32 max + EXPECT_NO_THROW(static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"ref_in\":2147483647}", "token", "exp_in", "exp_at", "ref_in"))); + + // 'ref_in' > int32 max + EXPECT_THROW( + static_cast(TokenCredentialImpl::ParseToken( + "{\"token\": \"x\",\"ref_in\":2147483648}", "token", "exp_in", "exp_at", "ref_in")), + std::exception); + // 'exp_at' negative EXPECT_THROW( static_cast(TokenCredentialImpl::ParseToken(