summaryrefslogtreecommitdiff
path: root/src/web_service/web_backend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/web_service/web_backend.cpp')
-rw-r--r--src/web_service/web_backend.cpp235
1 files changed, 141 insertions, 94 deletions
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 787b0fbcb..b7737b615 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -3,9 +3,11 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cstdlib> 5#include <cstdlib>
6#include <mutex>
6#include <string> 7#include <string>
7#include <thread>
8#include <LUrlParser.h> 8#include <LUrlParser.h>
9#include <httplib.h>
10#include "common/common_types.h"
9#include "common/logging/log.h" 11#include "common/logging/log.h"
10#include "common/web_result.h" 12#include "common/web_result.h"
11#include "core/settings.h" 13#include "core/settings.h"
@@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443;
20 22
21constexpr u32 TIMEOUT_SECONDS = 30; 23constexpr u32 TIMEOUT_SECONDS = 30;
22 24
23Client::JWTCache Client::jwt_cache{}; 25struct Client::Impl {
26 Impl(std::string host, std::string username, std::string token)
27 : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
28 std::lock_guard<std::mutex> lock(jwt_cache.mutex);
29 if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
30 jwt = jwt_cache.jwt;
31 }
32 }
33
34 /// A generic function handles POST, GET and DELETE request together
35 Common::WebResult GenericJson(const std::string& method, const std::string& path,
36 const std::string& data, bool allow_anonymous) {
37 if (jwt.empty()) {
38 UpdateJWT();
39 }
40
41 if (jwt.empty() && !allow_anonymous) {
42 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
43 return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
44 "Credentials needed"};
45 }
46
47 auto result = GenericJson(method, path, data, jwt);
48 if (result.result_string == "401") {
49 // Try again with new JWT
50 UpdateJWT();
51 result = GenericJson(method, path, data, jwt);
52 }
24 53
25Client::Client(const std::string& host, const std::string& username, const std::string& token) 54 return result;
26 : host(host), username(username), token(token) {
27 std::lock_guard<std::mutex> lock(jwt_cache.mutex);
28 if (username == jwt_cache.username && token == jwt_cache.token) {
29 jwt = jwt_cache.jwt;
30 } 55 }
31}
32 56
33Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, 57 /**
34 const std::string& data, const std::string& jwt, 58 * A generic function with explicit authentication method specified
35 const std::string& username, const std::string& token) { 59 * JWT is used if the jwt parameter is not empty
36 if (cli == nullptr) { 60 * username + token is used if jwt is empty but username and token are not empty
37 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); 61 * anonymous if all of jwt, username and token are empty
38 int port; 62 */
39 if (parsedUrl.m_Scheme == "http") { 63 Common::WebResult GenericJson(const std::string& method, const std::string& path,
40 if (!parsedUrl.GetPort(&port)) { 64 const std::string& data, const std::string& jwt = "",
41 port = HTTP_PORT; 65 const std::string& username = "", const std::string& token = "") {
42 } 66 if (cli == nullptr) {
43 cli = 67 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
44 std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS); 68 int port;
45 } else if (parsedUrl.m_Scheme == "https") { 69 if (parsedUrl.m_Scheme == "http") {
46 if (!parsedUrl.GetPort(&port)) { 70 if (!parsedUrl.GetPort(&port)) {
47 port = HTTPS_PORT; 71 port = HTTP_PORT;
72 }
73 cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port,
74 TIMEOUT_SECONDS);
75 } else if (parsedUrl.m_Scheme == "https") {
76 if (!parsedUrl.GetPort(&port)) {
77 port = HTTPS_PORT;
78 }
79 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
80 TIMEOUT_SECONDS);
81 } else {
82 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
83 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
48 } 84 }
49 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
50 TIMEOUT_SECONDS);
51 } else {
52 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
53 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
54 } 85 }
55 } 86 if (cli == nullptr) {
56 if (cli == nullptr) { 87 LOG_ERROR(WebService, "Invalid URL {}", host + path);
57 LOG_ERROR(WebService, "Invalid URL {}", host + path); 88 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
58 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; 89 }
59 }
60 90
61 httplib::Headers params; 91 httplib::Headers params;
62 if (!jwt.empty()) { 92 if (!jwt.empty()) {
63 params = { 93 params = {
64 {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, 94 {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
65 }; 95 };
66 } else if (!username.empty()) { 96 } else if (!username.empty()) {
67 params = { 97 params = {
68 {std::string("x-username"), username}, 98 {std::string("x-username"), username},
69 {std::string("x-token"), token}, 99 {std::string("x-token"), token},
100 };
101 }
102
103 params.emplace(std::string("api-version"),
104 std::string(API_VERSION.begin(), API_VERSION.end()));
105 if (method != "GET") {
106 params.emplace(std::string("Content-Type"), std::string("application/json"));
70 }; 107 };
71 }
72 108
73 params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end())); 109 httplib::Request request;
74 if (method != "GET") { 110 request.method = method;
75 params.emplace(std::string("Content-Type"), std::string("application/json")); 111 request.path = path;
76 }; 112 request.headers = params;
113 request.body = data;
77 114
78 httplib::Request request; 115 httplib::Response response;
79 request.method = method;
80 request.path = path;
81 request.headers = params;
82 request.body = data;
83 116
84 httplib::Response response; 117 if (!cli->send(request, response)) {
118 LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
119 return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
120 }
85 121
86 if (!cli->send(request, response)) { 122 if (response.status >= 400) {
87 LOG_ERROR(WebService, "{} to {} returned null", method, host + path); 123 LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
88 return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; 124 response.status);
89 } 125 return Common::WebResult{Common::WebResult::Code::HttpError,
126 std::to_string(response.status)};
127 }
90 128
91 if (response.status >= 400) { 129 auto content_type = response.headers.find("content-type");
92 LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
93 response.status);
94 return Common::WebResult{Common::WebResult::Code::HttpError,
95 std::to_string(response.status)};
96 }
97 130
98 auto content_type = response.headers.find("content-type"); 131 if (content_type == response.headers.end()) {
132 LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
133 return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
134 }
99 135
100 if (content_type == response.headers.end()) { 136 if (content_type->second.find("application/json") == std::string::npos &&
101 LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); 137 content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
102 return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; 138 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
139 content_type->second);
140 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
141 }
142 return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
103 } 143 }
104 144
105 if (content_type->second.find("application/json") == std::string::npos && 145 // Retrieve a new JWT from given username and token
106 content_type->second.find("text/html; charset=utf-8") == std::string::npos) { 146 void UpdateJWT() {
107 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, 147 if (username.empty() || token.empty()) {
108 content_type->second); 148 return;
109 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; 149 }
110 }
111 return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
112}
113 150
114void Client::UpdateJWT() {
115 if (!username.empty() && !token.empty()) {
116 auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); 151 auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
117 if (result.result_code != Common::WebResult::Code::Success) { 152 if (result.result_code != Common::WebResult::Code::Success) {
118 LOG_ERROR(WebService, "UpdateJWT failed"); 153 LOG_ERROR(WebService, "UpdateJWT failed");
@@ -123,27 +158,39 @@ void Client::UpdateJWT() {
123 jwt_cache.jwt = jwt = result.returned_data; 158 jwt_cache.jwt = jwt = result.returned_data;
124 } 159 }
125 } 160 }
126}
127 161
128Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, 162 std::string host;
129 const std::string& data, bool allow_anonymous) { 163 std::string username;
130 if (jwt.empty()) { 164 std::string token;
131 UpdateJWT(); 165 std::string jwt;
132 } 166 std::unique_ptr<httplib::Client> cli;
167
168 struct JWTCache {
169 std::mutex mutex;
170 std::string username;
171 std::string token;
172 std::string jwt;
173 };
174 static inline JWTCache jwt_cache;
175};
133 176
134 if (jwt.empty() && !allow_anonymous) { 177Client::Client(std::string host, std::string username, std::string token)
135 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); 178 : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
136 return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
137 }
138 179
139 auto result = GenericJson(method, path, data, jwt); 180Client::~Client() = default;
140 if (result.result_string == "401") { 181
141 // Try again with new JWT 182Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
142 UpdateJWT(); 183 bool allow_anonymous) {
143 result = GenericJson(method, path, data, jwt); 184 return impl->GenericJson("POST", path, data, allow_anonymous);
144 } 185}
186
187Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
188 return impl->GenericJson("GET", path, "", allow_anonymous);
189}
145 190
146 return result; 191Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
192 bool allow_anonymous) {
193 return impl->GenericJson("DELETE", path, data, allow_anonymous);
147} 194}
148 195
149} // namespace WebService 196} // namespace WebService