AccessToken TokenCredentialImpl::ParseToken()

in sdk/identity/azure-identity/src/token_credential_impl.cpp [258:477]


AccessToken TokenCredentialImpl::ParseToken(
    std::string const& jsonString,
    std::string const& accessTokenPropertyName,
    std::string const& expiresInPropertyName,
    std::vector<std::string> const& expiresOnPropertyNames,
    std::string const& refreshInPropertyName,
    bool proactiveRenewal,
    int utcDiffSeconds)
{
  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,
        parsedJson,
        accessTokenPropertyName,
        expiresInPropertyName,
        expiresOnPropertyNames);
  }

  AccessToken accessToken;
  accessToken.Token = parsedJson[accessTokenPropertyName].get<std::string>();
  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<std::int64_t>();
        if (value <= MaxExpirationInSeconds)
        {
          static_assert(
              MaxExpirationInSeconds <= (std::numeric_limits<std::int32_t>::max)(),
              "Can safely cast to int32");

          accessToken.ExpiresOn += std::chrono::seconds(static_cast<std::int32_t>(value));
          return accessToken;
        }
      }
      catch (std::exception const&)
      {
        // refreshIn.get<std::int64_t>() has thrown, we may throw later.
      }
    }
  }

  if (parsedJson.contains(expiresInPropertyName))
  {
    auto const& expiresIn = parsedJson[expiresInPropertyName];

    if (expiresIn.is_number_unsigned())
    {
      try
      {
        // 'expires_in' as number (seconds until expiration)
        auto const value = expiresIn.get<std::int64_t>();
        if (value <= MaxExpirationInSeconds)
        {
          static_assert(
              MaxExpirationInSeconds <= (std::numeric_limits<std::int32_t>::max)(),
              "Can safely cast to int32");

          auto expiresInSeconds = std::chrono::seconds(static_cast<std::int32_t>(value));
          accessToken.ExpiresOn
              += proactiveRenewal ? GetProactiveRenewalSeconds(expiresInSeconds) : expiresInSeconds;
          return accessToken;
        }
      }
      catch (std::exception const&)
      {
        // expiresIn.get<std::int64_t>() has thrown, we may throw later.
      }
    }

    if (expiresIn.is_string())
    {
      try
      {
        // 'expires_in' as numeric string (seconds until expiration)
        static_assert(
            MaxExpirationInSeconds <= (std::numeric_limits<std::int32_t>::max)(),
            "Can safely cast to int32");

        auto expiresInSeconds = std::chrono::seconds(static_cast<std::int32_t>(
            ParseNumericExpiration(expiresIn.get<std::string>(), MaxExpirationInSeconds)));
        accessToken.ExpiresOn
            += proactiveRenewal ? GetProactiveRenewalSeconds(expiresInSeconds) : expiresInSeconds;

        return accessToken;
      }
      catch (std::exception const&)
      {
        // ParseNumericExpiration() has thrown, we may throw later.
      }
    }
  }

  std::vector<std::string> nonEmptyExpiresOnPropertyNames;
  std::copy_if(
      expiresOnPropertyNames.begin(),
      expiresOnPropertyNames.end(),
      std::back_inserter(nonEmptyExpiresOnPropertyNames),
      [](auto const& propertyName) { return !propertyName.empty(); });

  if (nonEmptyExpiresOnPropertyNames.empty())
  {
    // The code was not able to parse the value of 'expires_in', and the caller did not pass any
    // 'expires_on' for us to find and try parse.
    ThrowJsonPropertyError(
        expiresInPropertyName,
        parsedJson,
        accessTokenPropertyName,
        expiresInPropertyName,
        nonEmptyExpiresOnPropertyNames);
  }

  for (auto const& expiresOnPropertyName : nonEmptyExpiresOnPropertyNames)
  {
    if (parsedJson.contains(expiresOnPropertyName))
    {
      auto const& expiresOn = parsedJson[expiresOnPropertyName];

      if (expiresOn.is_number_unsigned())
      {
        try
        {
          // 'expires_on' as number (posix time representing an absolute timestamp)
          auto const value = expiresOn.get<std::int64_t>();
          if (value <= MaxPosixTimestamp)
          {
            accessToken.ExpiresOn = proactiveRenewal
                ? GetProactiveRenewalDateTime(value)
                : PosixTimeConverter::PosixTimeToDateTime(value);
            return accessToken;
          }
        }
        catch (std::exception const&)
        {
          // expiresIn.get<std::int64_t>() has thrown, we may throw later.
        }
      }

      auto const tzOffsetStr = TimeZoneOffsetAsString(utcDiffSeconds);
      if (expiresOn.is_string())
      {
        bool successfulParse = false;
        auto const expiresOnAsString = expiresOn.get<std::string>();
        for (auto const& parse : {
                 std::function<DateTime(std::string const&)>([&](auto const& s) {
                   // 'expires_on' as RFC3339 date string (absolute timestamp)
                   auto dateTime = DateTime::Parse(s + tzOffsetStr, DateTime::DateFormat::Rfc3339);
                   return proactiveRenewal ? GetProactiveRenewalDateTime(
                              PosixTimeConverter::DateTimeToPosixTime(dateTime))
                                           : dateTime;
                 }),
                 std::function<DateTime(std::string const&)>([&](auto const& s) {
                   // 'expires_on' as numeric string (posix time representing an absolute timestamp)
                   auto value = ParseNumericExpiration(s, MaxPosixTimestamp);
                   return proactiveRenewal ? GetProactiveRenewalDateTime(value)
                                           : PosixTimeConverter::PosixTimeToDateTime(value);
                 }),
                 std::function<DateTime(std::string const&)>([&](auto const& s) {
                   // 'expires_on' as RFC1123 date string (absolute timestamp)
                   auto dateTime = DateTime::Parse(s, DateTime::DateFormat::Rfc1123);
                   return proactiveRenewal ? GetProactiveRenewalDateTime(
                              PosixTimeConverter::DateTimeToPosixTime(dateTime))
                                           : dateTime;
                 }),
             })
        {
          try
          {
            accessToken.ExpiresOn = parse(expiresOnAsString);
            // Workaround for Warning C26800 - Use of a moved from object: 'accessToken'
            // (lifetime.1) on MSVC version 14.40.33807+.
            // Returning accessToken here directly causes the warning.
            successfulParse = true;
            break;
          }
          catch (std::exception const&)
          {
            // parse() has thrown, we may throw later.
          }
        }
        if (successfulParse)
        {
          return accessToken;
        }
      }
    }
  }

  ThrowJsonPropertyError(
      nonEmptyExpiresOnPropertyNames.back(),
      parsedJson,
      accessTokenPropertyName,
      expiresInPropertyName,
      nonEmptyExpiresOnPropertyNames);
}