2020
2121#include < boost/property_tree/json_parser.hpp>
2222#include < boost/property_tree/ptree.hpp>
23+ #include < charconv>
2324#include < cstdint>
2425#include < sstream>
2526#include < stdexcept>
27+ #include < system_error>
2628
2729#include " InitialAuthData.h"
2830#include " lib/Base64Utils.h"
@@ -34,6 +36,11 @@ namespace pulsar {
3436
3537const std::string TlsClientAuthFlow::DEFAULT_CLIENT_ID = " pulsar-client" ;
3638namespace {
39+ constexpr int DEFAULT_OAUTH2_CONNECT_TIMEOUT_SECONDS = 10 ;
40+ constexpr int DEFAULT_OAUTH2_REQUEST_TIMEOUT_SECONDS = 30 ;
41+ constexpr char CONNECT_TIMEOUT_PARAM [] = " connect_timeout_seconds" ;
42+ constexpr char REQUEST_TIMEOUT_PARAM [] = " request_timeout_seconds" ;
43+
3744enum class OAuth2TokenEndpointAuthMethod : std::uint8_t
3845{
3946 ClientSecretPost,
@@ -60,8 +67,37 @@ std::string toFlowName(OAuth2TokenEndpointAuthMethod authMethod) {
6067 return " ClientCredentialFlow" ;
6168 }
6269}
70+
71+ int parsePositiveTimeout (const ParamMap& params, const char * name, int defaultValue) {
72+ const auto it = params.find (name);
73+ if (it == params.end ()) {
74+ return defaultValue;
75+ }
76+
77+ const auto & rawValue = it->second ;
78+ int value = 0 ;
79+ const auto result = std::from_chars (rawValue.data (), rawValue.data () + rawValue.size (), value);
80+ if (rawValue.empty () || result.ec != std::errc () || result.ptr != rawValue.data () + rawValue.size () ||
81+ value <= 0 ) {
82+ throw std::invalid_argument (std::string (" OAuth2 parameter " ) + name + " must be a positive integer" );
83+ }
84+ return value;
85+ }
86+
87+ CurlWrapper::Options createHttpOptions (const Oauth2HttpTimeouts& timeouts) {
88+ CurlWrapper::Options options;
89+ options.connectTimeoutInSeconds = timeouts.connectTimeoutInSeconds ;
90+ options.timeoutInSeconds = timeouts.requestTimeoutInSeconds ;
91+ return options;
92+ }
6393} // namespace
6494
95+ Oauth2HttpTimeouts::Oauth2HttpTimeouts (const ParamMap& params)
96+ : connectTimeoutInSeconds(
97+ parsePositiveTimeout (params, CONNECT_TIMEOUT_PARAM , DEFAULT_OAUTH2_CONNECT_TIMEOUT_SECONDS )),
98+ requestTimeoutInSeconds(
99+ parsePositiveTimeout (params, REQUEST_TIMEOUT_PARAM , DEFAULT_OAUTH2_REQUEST_TIMEOUT_SECONDS )) {}
100+
65101// AuthDataOauth2
66102
67103AuthDataOauth2::AuthDataOauth2 (const std::string& accessToken) { accessToken_ = accessToken; }
@@ -265,16 +301,17 @@ static std::unique_ptr<CurlWrapper::TlsContext> createTlsContext(const std::stri
265301 return tlsContext;
266302}
267303
268- static std::string fetchTokenEndpoint (const std::string& issuerUrl,
269- const CurlWrapper::TlsContext* tlsContext ) {
304+ static std::string fetchTokenEndpoint (const std::string& issuerUrl, const CurlWrapper::TlsContext* tlsContext,
305+ const Oauth2HttpTimeouts& timeouts ) {
270306 const auto wellKnownUrl = getWellKnownUrl (issuerUrl);
271307 CurlWrapper curl;
272308 if (!curl.init ()) {
273309 LOG_ERROR (" Failed to initialize curl" );
274310 return " " ;
275311 }
276312
277- auto result = curl.get (wellKnownUrl, " Accept: application/json" , {}, tlsContext);
313+ const auto options = createHttpOptions (timeouts);
314+ auto result = curl.get (wellKnownUrl, " Accept: application/json" , options, tlsContext);
278315 if (!result.error .empty ()) {
279316 LOG_ERROR (" Failed to get the well-known configuration " << issuerUrl << " : " << result.error );
280317 return " " ;
@@ -305,6 +342,11 @@ static std::string fetchTokenEndpoint(const std::string& issuerUrl,
305342 << issuerUrl << " . response Code " << responseCode);
306343 }
307344 break ;
345+ case CURLE_OPERATION_TIMEDOUT :
346+ LOG_ERROR (" Timed out retrieving OAuth2 issuer metadata from "
347+ << issuerUrl << " (connect timeout: " << timeouts.connectTimeoutInSeconds
348+ << " seconds, request timeout: " << timeouts.requestTimeoutInSeconds << " seconds)" );
349+ break ;
308350 default :
309351 LOG_ERROR (" Response failed for getting the well-known configuration "
310352 << issuerUrl << " . Error Code " << res << " : " << errorBuffer);
@@ -315,7 +357,8 @@ static std::string fetchTokenEndpoint(const std::string& issuerUrl,
315357
316358static Oauth2TokenResultPtr fetchOauth2Token (const std::string& tokenEndpoint, const ParamMap& params,
317359 const CurlWrapper::TlsContext* tlsContext,
318- OAuth2TokenEndpointAuthMethod authMethod) {
360+ OAuth2TokenEndpointAuthMethod authMethod,
361+ const Oauth2HttpTimeouts& timeouts) {
319362 Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr (new Oauth2TokenResult ());
320363 if (tokenEndpoint.empty ()) {
321364 return resultPtr;
@@ -333,7 +376,7 @@ static Oauth2TokenResultPtr fetchOauth2Token(const std::string& tokenEndpoint, c
333376 }
334377 LOG_DEBUG (" Generate URL encoded body for " << toFlowName (authMethod) << " : " << postData);
335378
336- CurlWrapper::Options options;
379+ auto options = createHttpOptions (timeouts) ;
337380 options.postFields = std::move (postData);
338381 auto result =
339382 curl.get (tokenEndpoint, " Content-Type: application/x-www-form-urlencoded" , options, tlsContext);
@@ -379,6 +422,11 @@ static Oauth2TokenResultPtr fetchOauth2Token(const std::string& tokenEndpoint, c
379422 << responseCode);
380423 }
381424 break ;
425+ case CURLE_OPERATION_TIMEDOUT :
426+ LOG_ERROR (" Timed out fetching OAuth2 token from "
427+ << tokenEndpoint << " (connect timeout: " << timeouts.connectTimeoutInSeconds
428+ << " seconds, request timeout: " << timeouts.requestTimeoutInSeconds << " seconds)" );
429+ break ;
382430 default :
383431 LOG_ERROR (" Response failed for token endpoint " << tokenEndpoint << " . ErrorCode " << res << " : "
384432 << errorBuffer);
@@ -389,7 +437,8 @@ static Oauth2TokenResultPtr fetchOauth2Token(const std::string& tokenEndpoint, c
389437}
390438
391439ClientCredentialFlow::ClientCredentialFlow (ParamMap& params)
392- : issuerUrl_(params[" issuer_url" ]),
440+ : httpTimeouts_(params),
441+ issuerUrl_(params[" issuer_url" ]),
393442 keyFile_(KeyFile::fromParamMap(params)),
394443 audience_(params[" audience" ]),
395444 scope_(params[" scope" ]),
@@ -408,7 +457,7 @@ void ClientCredentialFlow::initialize() {
408457 }
409458
410459 const auto tlsContext = createTlsContext (tlsTrustCertsFilePath_, tlsCertFilePath_, tlsKeyFilePath_);
411- this ->tokenEndPoint_ = fetchTokenEndpoint (issuerUrl_, tlsContext.get ());
460+ this ->tokenEndPoint_ = fetchTokenEndpoint (issuerUrl_, tlsContext.get (), httpTimeouts_ );
412461 if (!this ->tokenEndPoint_ .empty ()) {
413462 LOG_DEBUG (" Get token endpoint: " << this ->tokenEndPoint_ );
414463 }
@@ -466,11 +515,12 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
466515 const auto params = generateParamMap ();
467516 const auto tlsContext = createTlsContext (tlsTrustCertsFilePath_, tlsCertFilePath_, tlsKeyFilePath_);
468517 return fetchOauth2Token (tokenEndPoint_, params, tlsContext.get (),
469- OAuth2TokenEndpointAuthMethod::ClientSecretPost);
518+ OAuth2TokenEndpointAuthMethod::ClientSecretPost, httpTimeouts_ );
470519}
471520
472521TlsClientAuthFlow::TlsClientAuthFlow (ParamMap& params)
473- : issuerUrl_(params[" issuer_url" ]),
522+ : httpTimeouts_(params),
523+ issuerUrl_(params[" issuer_url" ]),
474524 clientId_(params[" client_id" ].empty() ? DEFAULT_CLIENT_ID : params["client_id"]),
475525 audience_(params[" audience" ]),
476526 scope_(params[" scope" ]),
@@ -494,7 +544,7 @@ void TlsClientAuthFlow::initialize() {
494544 LOG_ERROR (" Failed to initialize TlsClientAuthFlow: tls_cert_file or tls_key_file is not set" );
495545 return ;
496546 }
497- this ->tokenEndPoint_ = fetchTokenEndpoint (issuerUrl_, tlsContext.get ());
547+ this ->tokenEndPoint_ = fetchTokenEndpoint (issuerUrl_, tlsContext.get (), httpTimeouts_ );
498548 if (!this ->tokenEndPoint_ .empty ()) {
499549 LOG_DEBUG (" Get token endpoint: " << this ->tokenEndPoint_ );
500550 }
@@ -523,7 +573,7 @@ Oauth2TokenResultPtr TlsClientAuthFlow::authenticate() {
523573 return resultPtr;
524574 }
525575 return fetchOauth2Token (tokenEndPoint_, params, tlsContext.get (),
526- OAuth2TokenEndpointAuthMethod::TlsClientAuth);
576+ OAuth2TokenEndpointAuthMethod::TlsClientAuth, httpTimeouts_ );
527577}
528578
529579// AuthOauth2
0 commit comments