From d5bb2a21aa634246df84ed1b56a49227ec19dc27 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 9 Oct 2018 14:45:22 -0400 Subject: [PATCH 01/10] telemetry_session: Remove unimplemented FinalizeAsyncJob prototype This isn't implemented anywhere, so it can just be removed. --- src/core/telemetry_session.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index bcf206b1b..905704fb2 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -30,8 +30,6 @@ public: field_collection.AddField(type, name, std::move(value)); } - static void FinalizeAsyncJob(); - private: Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session std::unique_ptr backend; ///< Backend interface that logs fields From 61627c20425f7522d70ae02decf4665ac058fdee Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 9 Oct 2018 14:47:24 -0400 Subject: [PATCH 02/10] telemetry_session: Add missing includes Prevents potential compilation issues in the future by including missing headers for certain functions and types. --- src/core/telemetry_session.cpp | 1 + src/core/telemetry_session.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 94270dbfe..9535ffee4 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/file_util.h" +#include "common/logging/log.h" #include "common/scm_rev.h" #ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 905704fb2..976e2d7ed 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "common/telemetry.h" namespace Core { From 7142d3cf7818ed8fdfdda0af2a3d8e02f8b5742c Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 9 Oct 2018 14:49:15 -0400 Subject: [PATCH 03/10] telemetry_session: Remove doxygen comment for a non-existent parameter There's no "func" parameter, so this can just be removed. --- src/core/telemetry_session.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 976e2d7ed..e37b6599e 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -52,7 +52,6 @@ u64 RegenerateTelemetryId(); * Verifies the username and token. * @param username Citra username to use for authentication. * @param token Citra token to use for authentication. - * @param func A function that gets exectued when the verification is finished * @returns Future with bool indicating whether the verification succeeded */ bool VerifyLogin(const std::string& username, const std::string& token); From c9013c481aaf2583150c9997876493c9c198e03b Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 10 Oct 2018 20:57:29 -0400 Subject: [PATCH 04/10] telemetry_json: Remove unnecessary includes Removes unused includes. Also rectifies a missing include. --- src/web_service/telemetry_json.cpp | 2 -- src/web_service/telemetry_json.h | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 033ea1ea4..e77950aa3 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -2,8 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include "common/assert.h" #include "common/detached_tasks.h" #include "web_service/telemetry_json.h" #include "web_service/web_backend.h" diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index 42a15b6a4..a10f91091 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include "common/announce_multiplayer_room.h" From 8747d93f6a42cf255256a88c41b80421adbfe08a Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 10 Oct 2018 20:59:25 -0400 Subject: [PATCH 05/10] telemetry_json: Take std::string parameters by value Taking them by const reference isn't advisable here, because it means the std::move calls were doing nothing and we were always copying the std::string instances. --- src/web_service/telemetry_json.cpp | 3 +-- src/web_service/telemetry_json.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index e77950aa3..b24e806a8 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -8,8 +8,7 @@ namespace WebService { -TelemetryJson::TelemetryJson(const std::string& host, const std::string& username, - const std::string& token) +TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token) : host(std::move(host)), username(std::move(username)), token(std::move(token)) {} TelemetryJson::~TelemetryJson() = default; diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index a10f91091..69e87ad7a 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -19,7 +19,7 @@ namespace WebService { */ class TelemetryJson : public Telemetry::VisitorInterface { public: - TelemetryJson(const std::string& host, const std::string& username, const std::string& token); + TelemetryJson(std::string host, std::string username, std::string token); ~TelemetryJson(); void Visit(const Telemetry::Field& field) override; From 131ce598000b002fa8cec74a0ce53e78bb602671 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 10 Oct 2018 21:00:39 -0400 Subject: [PATCH 06/10] telemetry_json: Add missing override specifier to the destructor of TelemetryJson --- src/web_service/telemetry_json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index 69e87ad7a..361243649 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -20,7 +20,7 @@ namespace WebService { class TelemetryJson : public Telemetry::VisitorInterface { public: TelemetryJson(std::string host, std::string username, std::string token); - ~TelemetryJson(); + ~TelemetryJson() override; void Visit(const Telemetry::Field& field) override; void Visit(const Telemetry::Field& field) override; From 25038aeb0de73d7630ed11a024311680ccd3074a Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 10 Oct 2018 21:04:19 -0400 Subject: [PATCH 07/10] telemetry_json: Use the PImpl idiom to avoid unnecessary dependency exposure Users of the web_service library shouldn't need to care about an external library like json.h. However, given it's exposed in our interface, this requires that other libraries publicly link in the JSON library. We can do better. By using the PImpl idiom, we can hide this dependency in the cpp file and remove the need to link that library in altogether. --- src/web_service/telemetry_json.cpp | 84 ++++++++++++++++++------------ src/web_service/telemetry_json.h | 18 +------ 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index b24e806a8..3c5590054 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -2,93 +2,113 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "common/detached_tasks.h" #include "web_service/telemetry_json.h" #include "web_service/web_backend.h" namespace WebService { +struct TelemetryJson::Impl { + Impl(std::string host, std::string username, std::string token) + : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {} + + nlohmann::json& TopSection() { + return sections[static_cast(Telemetry::FieldType::None)]; + } + + const nlohmann::json& TopSection() const { + return sections[static_cast(Telemetry::FieldType::None)]; + } + + template + void Serialize(Telemetry::FieldType type, const std::string& name, T value) { + sections[static_cast(type)][name] = value; + } + + void SerializeSection(Telemetry::FieldType type, const std::string& name) { + TopSection()[name] = sections[static_cast(type)]; + } + + nlohmann::json output; + std::array sections; + std::string host; + std::string username; + std::string token; +}; + TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token) - : host(std::move(host)), username(std::move(username)), token(std::move(token)) {} + : impl{std::make_unique(std::move(host), std::move(username), std::move(token))} {} TelemetryJson::~TelemetryJson() = default; -template -void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { - sections[static_cast(type)][name] = value; -} - -void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { - TopSection()[name] = sections[static_cast(type)]; -} - void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); + impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); } void TelemetryJson::Visit(const Telemetry::Field& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue().count()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count()); } void TelemetryJson::Complete() { - SerializeSection(Telemetry::FieldType::App, "App"); - SerializeSection(Telemetry::FieldType::Session, "Session"); - SerializeSection(Telemetry::FieldType::Performance, "Performance"); - SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); - SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); - SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); + impl->SerializeSection(Telemetry::FieldType::App, "App"); + impl->SerializeSection(Telemetry::FieldType::Session, "Session"); + impl->SerializeSection(Telemetry::FieldType::Performance, "Performance"); + impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); + impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); + impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); - auto content = TopSection().dump(); + auto content = impl->TopSection().dump(); // Send the telemetry async but don't handle the errors since they were written to the log Common::DetachedTasks::AddTask( - [host{this->host}, username{this->username}, token{this->token}, content]() { + [host{impl->host}, username{impl->username}, token{impl->token}, content]() { Client{host, username, token}.PostJson("/telemetry", content, true); }); } diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index 361243649..4b4bef3bc 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -4,10 +4,8 @@ #pragma once -#include #include #include -#include #include "common/announce_multiplayer_room.h" #include "common/telemetry.h" @@ -40,20 +38,8 @@ public: void Complete() override; private: - nlohmann::json& TopSection() { - return sections[static_cast(Telemetry::FieldType::None)]; - } - - template - void Serialize(Telemetry::FieldType type, const std::string& name, T value); - - void SerializeSection(Telemetry::FieldType type, const std::string& name); - - nlohmann::json output; - std::array sections; - std::string host; - std::string username; - std::string token; + struct Impl; + std::unique_ptr impl; }; } // namespace WebService From 8b98560ebbd0d218b50e95f8e26bc3dd3acadbfb Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 10 Oct 2018 21:23:41 -0400 Subject: [PATCH 08/10] web_backend: Make Client use the PImpl idiom Like with TelemetryJson, we can make the implementation details private and avoid the need to expose httplib to external libraries that need to use the Client class. --- src/web_service/telemetry_json.cpp | 1 + src/web_service/verify_login.cpp | 1 + src/web_service/web_backend.cpp | 256 +++++++++++++++++------------ src/web_service/web_backend.h | 56 +------ 4 files changed, 161 insertions(+), 153 deletions(-) diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 3c5590054..0a8f2bd9e 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -4,6 +4,7 @@ #include #include "common/detached_tasks.h" +#include "common/web_result.h" #include "web_service/telemetry_json.h" #include "web_service/web_backend.h" diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp index 124aa3863..ca4b43b93 100644 --- a/src/web_service/verify_login.cpp +++ b/src/web_service/verify_login.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include "common/web_result.h" #include "web_service/verify_login.h" #include "web_service/web_backend.h" diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 9b824e47d..6329c6a0c 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -3,11 +3,12 @@ // Refer to the license.txt file included. #include +#include #include -#include #include #include #include "common/announce_multiplayer_room.h" +#include "common/common_types.h" #include "common/logging/log.h" #include "web_service/web_backend.h" @@ -20,101 +21,132 @@ constexpr int HTTPS_PORT = 443; constexpr std::size_t 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) { - std::lock_guard lock(jwt_cache.mutex); - if (username == jwt_cache.username && token == jwt_cache.token) { - jwt = jwt_cache.jwt; - } -} - -Client::~Client() = default; - -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) { - if (cli == nullptr) { - auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); - int port; - if (parsedUrl.m_Scheme == "http") { - if (!parsedUrl.GetPort(&port)) { - port = HTTP_PORT; - } - cli = - std::make_unique(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS); - } else if (parsedUrl.m_Scheme == "https") { - if (!parsedUrl.GetPort(&port)) { - port = HTTPS_PORT; - } - cli = std::make_unique(parsedUrl.m_Host.c_str(), port, - TIMEOUT_SECONDS); - } else { - LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); - return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; +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 lock(jwt_cache.mutex); + if (this->username == jwt_cache.username && this->token == jwt_cache.token) { + jwt = jwt_cache.jwt; } } - if (cli == nullptr) { - LOG_ERROR(WebService, "Invalid URL {}", host + path); - return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; + + /// 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; } - httplib::Headers params; - if (!jwt.empty()) { - params = { - {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, - }; - } else if (!username.empty()) { - params = { - {std::string("x-username"), username}, - {std::string("x-token"), token}, + /** + * 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; + if (parsedUrl.m_Scheme == "http") { + if (!parsedUrl.GetPort(&port)) { + port = HTTP_PORT; + } + cli = std::make_unique(parsedUrl.m_Host.c_str(), port, + TIMEOUT_SECONDS); + } else if (parsedUrl.m_Scheme == "https") { + if (!parsedUrl.GetPort(&port)) { + port = HTTPS_PORT; + } + cli = std::make_unique(parsedUrl.m_Host.c_str(), port, + TIMEOUT_SECONDS); + } else { + LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); + return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; + } + } + if (cli == nullptr) { + LOG_ERROR(WebService, "Invalid URL {}", host + path); + return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; + } + + httplib::Headers params; + if (!jwt.empty()) { + params = { + {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, + }; + } else if (!username.empty()) { + params = { + {std::string("x-username"), username}, + {std::string("x-token"), token}, + }; + } + + 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")); }; + + httplib::Request request; + request.method = method; + request.path = path; + request.headers = params; + request.body = data; + + httplib::Response response; + + if (!cli->send(request, response)) { + LOG_ERROR(WebService, "{} to {} returned null", method, host + path); + return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; + } + + if (response.status >= 400) { + LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, + response.status); + return Common::WebResult{Common::WebResult::Code::HttpError, + std::to_string(response.status)}; + } + + auto content_type = response.headers.find("content-type"); + + if (content_type == response.headers.end()) { + LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); + return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; + } + + if (content_type->second.find("application/json") == std::string::npos && + content_type->second.find("text/html; charset=utf-8") == std::string::npos) { + LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, + content_type->second); + return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; + } + return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; } - 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")); - }; + // Retrieve a new JWT from given username and token + void UpdateJWT() { + if (username.empty() || token.empty()) { + return; + } - httplib::Request request; - request.method = method; - request.path = path; - request.headers = params; - request.body = data; - - httplib::Response response; - - if (!cli->send(request, response)) { - LOG_ERROR(WebService, "{} to {} returned null", method, host + path); - return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; - } - - if (response.status >= 400) { - LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, - response.status); - return Common::WebResult{Common::WebResult::Code::HttpError, - std::to_string(response.status)}; - } - - auto content_type = response.headers.find("content-type"); - - if (content_type == response.headers.end()) { - LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); - return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; - } - - if (content_type->second.find("application/json") == std::string::npos && - content_type->second.find("text/html; charset=utf-8") == std::string::npos) { - LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, - content_type->second); - return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; - } - return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; -} - -void Client::UpdateJWT() { - if (!username.empty() && !token.empty()) { auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); if (result.result_code != Common::WebResult::Code::Success) { LOG_ERROR(WebService, "UpdateJWT failed"); @@ -125,27 +157,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 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(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 diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h index 6cdf35b48..c637e09df 100644 --- a/src/web_service/web_backend.h +++ b/src/web_service/web_backend.h @@ -4,22 +4,18 @@ #pragma once -#include -#include +#include #include -#include -#include "common/announce_multiplayer_room.h" -#include "common/common_types.h" -namespace httplib { -class Client; +namespace Common { +struct WebResult; } namespace WebService { class Client { public: - Client(const std::string& host, const std::string& username, const std::string& token); + Client(std::string host, std::string username, std::string token); ~Client(); /** @@ -30,9 +26,7 @@ public: * @return the result of the request. */ Common::WebResult PostJson(const std::string& path, const std::string& data, - bool allow_anonymous) { - return GenericJson("POST", path, data, allow_anonymous); - } + bool allow_anonymous); /** * Gets JSON from the specified path. @@ -40,9 +34,7 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult GetJson(const std::string& path, bool allow_anonymous) { - return GenericJson("GET", path, "", allow_anonymous); - } + Common::WebResult GetJson(const std::string& path, bool allow_anonymous); /** * Deletes JSON to the specified path. @@ -52,41 +44,11 @@ public: * @return the result of the request. */ Common::WebResult DeleteJson(const std::string& path, const std::string& data, - bool allow_anonymous) { - return GenericJson("DELETE", path, data, allow_anonymous); - } + bool allow_anonymous); private: - /// 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); - - /** - * 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 = ""); - - // Retrieve a new JWT from given username and token - void UpdateJWT(); - - std::string host; - std::string username; - std::string token; - std::string jwt; - std::unique_ptr cli; - - struct JWTCache { - std::mutex mutex; - std::string username; - std::string token; - std::string jwt; - }; - static JWTCache jwt_cache; + struct Impl; + std::unique_ptr impl; }; } // namespace WebService From 6fb673764214cc585af470f14bccebf2fa7de321 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 10 Oct 2018 21:37:19 -0400 Subject: [PATCH 09/10] core/CMakeLists: Make all web_service-related libraries private Now that all external dependencies are hidden, we can remove json-headers from the publically linked libraries, as the use of this library is now completely hidden from external users of the web_service library. We can also make the web_services library private as well, considering it's not a requirement. If a library needs to link in web_service, it should be done explicitly -- not via indirect linking. --- src/core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2b9838a84..c30ddcbdd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -446,7 +446,7 @@ target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives) if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) - target_link_libraries(core PRIVATE json-headers web_service) + target_link_libraries(core PRIVATE web_service) endif() if (ARCHITECTURE_x86_64) From d28233961bb83b155e2ddf25f3a2f3ae3ff6d278 Mon Sep 17 00:00:00 2001 From: fearlessTobi Date: Fri, 26 Oct 2018 17:01:00 +0200 Subject: [PATCH 10/10] Put WebResult into a seperate file --- src/common/CMakeLists.txt | 1 + src/common/announce_multiplayer_room.h | 18 +----------------- src/common/web_result.h | 25 +++++++++++++++++++++++++ src/web_service/web_backend.cpp | 3 ++- 4 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 src/common/web_result.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7a1c5bc4c..5eb5da8fb 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -90,6 +90,7 @@ add_library(common STATIC timer.cpp timer.h vector_math.h + web_result.h ) if(ARCHITECTURE_x86_64) diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h index 811f78d8c..5f9fc8ec2 100644 --- a/src/common/announce_multiplayer_room.h +++ b/src/common/announce_multiplayer_room.h @@ -9,23 +9,7 @@ #include #include #include "common/common_types.h" - -namespace Common { -struct WebResult { - enum class Code : u32 { - Success, - InvalidURL, - CredentialsMissing, - LibError, - HttpError, - WrongContent, - NoWebservice, - }; - Code result_code; - std::string result_string; - std::string returned_data; -}; -} // namespace Common +#include "common/web_result.h" namespace AnnounceMultiplayerRoom { diff --git a/src/common/web_result.h b/src/common/web_result.h new file mode 100644 index 000000000..286c2b0f4 --- /dev/null +++ b/src/common/web_result.h @@ -0,0 +1,25 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" + +namespace Common { +struct WebResult { + enum class Code : u32 { + Success, + InvalidURL, + CredentialsMissing, + LibError, + HttpError, + WrongContent, + NoWebservice, + }; + Code result_code; + std::string result_string; + std::string returned_data; +}; +} // namespace Common diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 6329c6a0c..84d6105d7 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -2,14 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include #include #include -#include "common/announce_multiplayer_room.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/web_result.h" #include "web_service/web_backend.h" namespace WebService {