// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include #include #include ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # define timegm _mkgmtime #endif // ZEN_PLATFORM_WINDOWS namespace zen { namespace httpclientauth { using namespace std::literals; std::function CreateFromStaticToken(HttpClientAccessToken Token) { return [Token]() { return Token; }; } std::function CreateFromStaticToken(std::string_view Token) { return CreateFromStaticToken( HttpClientAccessToken{.Value = fmt::format("Bearer {}"sv, Token), .ExpireTime = HttpClientAccessToken::TimePoint::max()}); } std::function CreateFromOAuthClientCredentials(const OAuthClientCredentialsParams& Params) { OAuthClientCredentialsParams OAuthParams(Params); return [OAuthParams]() { using namespace std::chrono; std::string Body = fmt::format("client_id={}&scope=cache_access&grant_type=client_credentials&client_secret={}"sv, OAuthParams.ClientId, OAuthParams.ClientSecret); cpr::Response Response = cpr::Post(cpr::Url{OAuthParams.Url}, cpr::Header{{"Content-Type", "application/x-www-form-urlencoded"}}, cpr::Body{std::move(Body)}); if (Response.error || Response.status_code != 200) { ZEN_WARN("Failed fetching OAuth access token {}. Reason: '{}'", OAuthParams.Url, Response.reason); return HttpClientAccessToken{}; } std::string JsonError; json11::Json Json = json11::Json::parse(Response.text, JsonError); if (JsonError.empty() == false) { ZEN_WARN("Unable to parse OAuth json response from {}. Reason: '{}'", OAuthParams.Url, JsonError); return HttpClientAccessToken{}; } std::string Token = Json["access_token"].string_value(); int64_t ExpiresInSeconds = static_cast(Json["expires_in"].int_value()); HttpClientAccessToken::TimePoint ExpireTime = HttpClientAccessToken::Clock::now() + seconds(ExpiresInSeconds); return HttpClientAccessToken{.Value = fmt::format("Bearer {}"sv, Token), .ExpireTime = ExpireTime}; }; } std::function CreateFromOpenIdProvider(AuthMgr& AuthManager, std::string_view OpenIdProvider) { return [&AuthManager = AuthManager, OpenIdProvider = std::string(OpenIdProvider)]() { AuthMgr::OpenIdAccessToken Token = AuthManager.GetOpenIdAccessToken(OpenIdProvider); return HttpClientAccessToken{.Value = Token.AccessToken, .ExpireTime = Token.ExpireTime}; }; } std::function CreateFromDefaultOpenIdProvider(AuthMgr& AuthManager) { return CreateFromOpenIdProvider(AuthManager, "Default"sv); } static HttpClientAccessToken GetOidcTokenFromExe(const std::filesystem::path& OidcExecutablePath, std::string_view CloudHost, bool Unattended) { Stopwatch Timer; CreateProcOptions ProcOptions; const std::filesystem::path AuthTokenPath(std::filesystem::temp_directory_path() / fmt::format(".zen-auth-{}", Oid::NewOid())); auto _ = MakeGuard([AuthTokenPath]() { RemoveFile(AuthTokenPath); }); const std::string ProcArgs = fmt::format("{} --AuthConfigUrl {} --OutFile {} --Unattended={}", OidcExecutablePath, CloudHost, AuthTokenPath, Unattended ? "true"sv : "false"sv); ZEN_DEBUG("Running: {}", ProcArgs); ProcessHandle Proc; Proc.Initialize(CreateProc(OidcExecutablePath, ProcArgs, ProcOptions)); if (!Proc.IsValid()) { throw std::runtime_error(fmt::format("failed to launch '{}'", OidcExecutablePath)); } int ExitCode = Proc.WaitExitCode(); auto EndTime = std::chrono::system_clock::now(); if (ExitCode == 0) { IoBuffer Body = IoBufferBuilder::MakeFromFile(AuthTokenPath); std::string JsonText(reinterpret_cast(Body.GetData()), Body.GetSize()); std::string JsonError; json11::Json Json = json11::Json::parse(JsonText, JsonError); if (JsonError.empty() == false) { ZEN_WARN("Unable to parse Oidcs json response from {}. Reason: '{}'", AuthTokenPath, JsonError); return HttpClientAccessToken{}; } std::string Token = Json["Token"].string_value(); std::string ExpiresAtUTCString = Json["ExpiresAtUtc"].string_value(); ZEN_ASSERT(!ExpiresAtUTCString.empty()); int Year = 0; int Month = 0; int Day = 0; int Hour = 0; int Minute = 0; int Second = 0; int Millisecond = 0; sscanf(ExpiresAtUTCString.c_str(), "%d-%d-%dT%d:%d:%d.%dZ", &Year, &Month, &Day, &Hour, &Minute, &Second, &Millisecond); std::tm Time = { Second, Minute, Hour, Day, Month - 1, Year - 1900, }; time_t UTCTime = timegm(&Time); HttpClientAccessToken::TimePoint ExpireTime = std::chrono::system_clock::from_time_t(UTCTime); ExpireTime += std::chrono::microseconds(Millisecond); return HttpClientAccessToken{.Value = fmt::format("Bearer {}"sv, Token), .ExpireTime = ExpireTime}; } else { ZEN_WARN("Failed running {} to get auth token, error code {}", OidcExecutablePath, ExitCode); } return HttpClientAccessToken{}; } std::optional> CreateFromOidcTokenExecutable(const std::filesystem::path& OidcExecutablePath, std::string_view CloudHost) { HttpClientAccessToken InitialToken = GetOidcTokenFromExe(OidcExecutablePath, CloudHost, false); if (InitialToken.IsValid()) { return [OidcExecutablePath = std::filesystem::path(OidcExecutablePath), CloudHost = std::string(CloudHost), InitialToken]() mutable { if (InitialToken.IsValid()) { HttpClientAccessToken Result = InitialToken; InitialToken = {}; return Result; } return GetOidcTokenFromExe(OidcExecutablePath, CloudHost, true); }; } return {}; } }} // namespace zen::httpclientauth