@ -3,9 +3,11 @@
// Refer to the license.txt file included.
# include <cstdlib>
# include <mutex>
# include <string>
# include <thread>
# include <LUrlParser.h>
# include <httplib.h>
# include "common/common_types.h"
# include "common/logging/log.h"
# include "common/web_result.h"
# include "core/settings.h"
@ -20,19 +22,47 @@ constexpr u32 HTTPS_PORT = 443;
constexpr u32 TIMEOUT_SECONDS = 30 ;
Client : : JWTCache Client : : jwt_cache { } ;
Client : : Client ( const std : : string & host , const std : : string & username , const std : : string & token )
: host ( host ) , username ( username ) , token ( token ) {
struct Client : : Impl {
Impl ( std : : string host , std : : string username , std : : string token )
: host { std : : move ( host ) } , username { std : : move ( username ) } , token { std : : move ( token ) } {
std : : lock_guard < std : : mutex > lock ( jwt_cache . mutex ) ;
if ( username = = jwt_cache . username & & token = = jwt_cache . token ) {
if ( this - > username = = jwt_cache . username & & this - > token = = jwt_cache . token ) {
jwt = jwt_cache . jwt ;
}
}
Common : : WebResult Client : : GenericJson ( const std : : string & method , const std : : string & path ,
const std : : string & data , const std : : string & jwt ,
const std : : string & username , const std : : string & token ) {
/// A generic function handles POST, GET and DELETE request together
Common : : WebResult GenericJson ( const std : : string & method , const std : : string & path ,
const std : : string & data , bool allow_anonymous ) {
if ( jwt . empty ( ) ) {
UpdateJWT ( ) ;
}
if ( jwt . empty ( ) & & ! allow_anonymous ) {
LOG_ERROR ( WebService , " Credentials must be provided for authenticated requests " ) ;
return Common : : WebResult { Common : : WebResult : : Code : : CredentialsMissing ,
" Credentials needed " } ;
}
auto result = GenericJson ( method , path , data , jwt ) ;
if ( result . result_string = = " 401 " ) {
// Try again with new JWT
UpdateJWT ( ) ;
result = GenericJson ( method , path , data , jwt ) ;
}
return result ;
}
/**
* A generic function with explicit authentication method specified
* JWT is used if the jwt parameter is not empty
* username + token is used if jwt is empty but username and token are not empty
* anonymous if all of jwt , username and token are empty
*/
Common : : WebResult GenericJson ( const std : : string & method , const std : : string & path ,
const std : : string & data , const std : : string & jwt = " " ,
const std : : string & username = " " , const std : : string & token = " " ) {
if ( cli = = nullptr ) {
auto parsedUrl = LUrlParser : : clParseURL : : ParseURL ( host ) ;
int port ;
@ -40,8 +70,8 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
if ( ! parsedUrl . GetPort ( & port ) ) {
port = HTTP_PORT ;
}
cli =
std : : make_unique < httplib : : Client > ( parsedUrl . m_Host . c_str ( ) , port , TIMEOUT_SECONDS ) ;
cli = std : : make_unique < httplib : : Client > ( parsedUrl . m_Host . c_str ( ) , port ,
TIMEOUT_SECONDS ) ;
} else if ( parsedUrl . m_Scheme = = " https " ) {
if ( ! parsedUrl . GetPort ( & port ) ) {
port = HTTPS_PORT ;
@ -70,7 +100,8 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
} ;
}
params . emplace ( std : : string ( " api-version " ) , std : : string ( API_VERSION . begin ( ) , API_VERSION . end ( ) ) ) ;
params . emplace ( std : : string ( " api-version " ) ,
std : : string ( API_VERSION . begin ( ) , API_VERSION . end ( ) ) ) ;
if ( method ! = " GET " ) {
params . emplace ( std : : string ( " Content-Type " ) , std : : string ( " application/json " ) ) ;
} ;
@ -111,8 +142,12 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
return Common : : WebResult { Common : : WebResult : : Code : : Success , " " , response . body } ;
}
void Client : : UpdateJWT ( ) {
if ( ! username . empty ( ) & & ! token . empty ( ) ) {
// Retrieve a new JWT from given username and token
void UpdateJWT ( ) {
if ( username . empty ( ) | | token . empty ( ) ) {
return ;
}
auto result = GenericJson ( " POST " , " /jwt/internal " , " " , " " , username , token ) ;
if ( result . result_code ! = Common : : WebResult : : Code : : Success ) {
LOG_ERROR ( WebService , " UpdateJWT failed " ) ;
@ -123,27 +158,39 @@ void Client::UpdateJWT() {
jwt_cache . jwt = jwt = result . returned_data ;
}
}
std : : string host ;
std : : string username ;
std : : string token ;
std : : string jwt ;
std : : unique_ptr < httplib : : Client > cli ;
struct JWTCache {
std : : mutex mutex ;
std : : string username ;
std : : string token ;
std : : string jwt ;
} ;
static inline JWTCache jwt_cache ;
} ;
Client : : Client ( std : : string host , std : : string username , std : : string token )
: impl { std : : make_unique < Impl > ( std : : move ( host ) , std : : move ( username ) , std : : move ( token ) ) } { }
Client : : ~ Client ( ) = default ;
Common : : WebResult Client : : PostJson ( const std : : string & path , const std : : string & data ,
bool allow_anonymous ) {
return impl - > GenericJson ( " POST " , path , data , allow_anonymous ) ;
}
Common : : WebResult Client : : GenericJson ( const std : : string & method , const std : : string & path ,
const std : : string & data , bool allow_anonymous ) {
if ( jwt . empty ( ) ) {
UpdateJWT ( ) ;
Common : : WebResult Client : : GetJson ( const std : : string & path , bool allow_anonymous ) {
return impl - > GenericJson ( " GET " , path , " " , allow_anonymous ) ;
}
if ( jwt . empty ( ) & & ! allow_anonymous ) {
LOG_ERROR ( WebService , " Credentials must be provided for authenticated requests " ) ;
return Common : : WebResult { Common : : WebResult : : Code : : CredentialsMissing , " Credentials needed " } ;
}
auto result = GenericJson ( method , path , data , jwt ) ;
if ( result . result_string = = " 401 " ) {
// Try again with new JWT
UpdateJWT ( ) ;
result = GenericJson ( method , path , data , jwt ) ;
}
return result ;
Common : : WebResult Client : : DeleteJson ( const std : : string & path , const std : : string & data ,
bool allow_anonymous ) {
return impl - > GenericJson ( " DELETE " , path , data , allow_anonymous ) ;
}
} // namespace WebService