| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- // Copyright 2017 Citra Emulator Project
- // Licensed under GPLv2 or any later version
- // Refer to the license.txt file included.
- #include <array>
- #include <cstdlib>
- #include <mutex>
- #include <string>
- #include <fmt/format.h>
- #include <httplib.h>
- #include "common/logging/log.h"
- #include "web_service/web_backend.h"
- #include "web_service/web_result.h"
- namespace WebService {
- constexpr std::array<const char, 1> API_VERSION{'1'};
- constexpr std::size_t TIMEOUT_SECONDS = 30;
- 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;
- }
- }
- /// A generic function handles POST, GET and DELETE request together
- WebResult GenericRequest(const std::string& method, const std::string& path,
- const std::string& data, bool allow_anonymous,
- const std::string& accept) {
- if (jwt.empty()) {
- UpdateJWT();
- }
- if (jwt.empty() && !allow_anonymous) {
- LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
- return WebResult{WebResult::Code::CredentialsMissing, "Credentials needed", ""};
- }
- auto result = GenericRequest(method, path, data, accept, jwt);
- if (result.result_string == "401") {
- // Try again with new JWT
- UpdateJWT();
- result = GenericRequest(method, path, data, accept, 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
- */
- WebResult GenericRequest(const std::string& method, const std::string& path,
- const std::string& data, const std::string& accept,
- const std::string& jwt = "", const std::string& username = "",
- const std::string& token = "") {
- if (cli == nullptr) {
- cli = std::make_unique<httplib::Client>(host.c_str());
- }
- if (!cli->is_valid()) {
- LOG_ERROR(WebService, "Client is invalid, skipping request!");
- return {};
- }
- cli->set_connection_timeout(TIMEOUT_SECONDS);
- cli->set_read_timeout(TIMEOUT_SECONDS);
- cli->set_write_timeout(TIMEOUT_SECONDS);
- 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;
- httplib::Error error;
- if (!cli->send(request, response, error)) {
- LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
- return WebResult{WebResult::Code::LibError, "Null response", ""};
- }
- if (response.status >= 400) {
- LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
- response.status);
- return WebResult{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 WebResult{WebResult::Code::WrongContent, "", ""};
- }
- if (content_type->second.find(accept) == std::string::npos) {
- LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
- content_type->second);
- return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
- }
- return WebResult{WebResult::Code::Success, "", response.body};
- }
- // Retrieve a new JWT from given username and token
- void UpdateJWT() {
- if (username.empty() || token.empty()) {
- return;
- }
- auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token);
- if (result.result_code != WebResult::Code::Success) {
- LOG_ERROR(WebService, "UpdateJWT failed");
- } else {
- std::lock_guard lock{jwt_cache.mutex};
- jwt_cache.username = username;
- jwt_cache.token = token;
- 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;
- WebResult Client::PostJson(const std::string& path, const std::string& data, bool allow_anonymous) {
- return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json");
- }
- WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
- return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json");
- }
- WebResult Client::DeleteJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
- return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json");
- }
- WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
- return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain");
- }
- WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
- return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png");
- }
- WebResult Client::GetExternalJWT(const std::string& audience) {
- return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false,
- "text/html");
- }
- } // namespace WebService
|