Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #include "server_account_manager.h"
18 : #include "base64.h"
19 : #include "jami/account_const.h"
20 : #include "fileutils.h"
21 :
22 : #include <opendht/http.h>
23 : #include <opendht/log.h>
24 : #include <opendht/thread_pool.h>
25 :
26 : #include "conversation_module.h"
27 : #include "jamiaccount.h"
28 : #include "manager.h"
29 :
30 : #include <algorithm>
31 : #include <string_view>
32 :
33 : using namespace std::literals;
34 :
35 : namespace jami {
36 :
37 : using Request = dht::http::Request;
38 :
39 : #define JAMI_PATH_LOGIN "/api/login"
40 : #define JAMI_PATH_AUTH "/api/auth"
41 : constexpr std::string_view PATH_DEVICE = JAMI_PATH_AUTH "/device";
42 : constexpr std::string_view PATH_DEVICES = JAMI_PATH_AUTH "/devices";
43 : constexpr std::string_view PATH_SEARCH = JAMI_PATH_AUTH "/directory/search";
44 : constexpr std::string_view PATH_CONTACTS = JAMI_PATH_AUTH "/contacts";
45 : constexpr std::string_view PATH_CONVERSATIONS = JAMI_PATH_AUTH "/conversations";
46 : constexpr std::string_view PATH_CONVERSATIONS_REQUESTS = JAMI_PATH_AUTH "/conversationRequests";
47 : constexpr std::string_view PATH_BLUEPRINT = JAMI_PATH_AUTH "/policyData";
48 :
49 0 : ServerAccountManager::ServerAccountManager(const std::string& accountId,
50 : const std::filesystem::path& path,
51 : const std::string& managerHostname,
52 0 : const std::string& nameServer)
53 : : AccountManager(accountId, path, nameServer)
54 0 : , managerHostname_(managerHostname)
55 0 : , logger_(Logger::dhtLogger()) {}
56 :
57 : void
58 0 : ServerAccountManager::setAuthHeaderFields(Request& request) const
59 : {
60 0 : request.set_header_field(restinio::http_field_t::authorization, "Bearer " + token_);
61 0 : }
62 :
63 : void
64 0 : ServerAccountManager::initAuthentication(PrivateKey key,
65 : std::string deviceName,
66 : std::unique_ptr<AccountCredentials> credentials,
67 : AuthSuccessCallback onSuccess,
68 : AuthFailureCallback onFailure,
69 : const OnChangeCallback& onChange)
70 : {
71 0 : auto ctx = std::make_shared<AuthContext>();
72 0 : ctx->accountId = accountId_;
73 0 : ctx->key = key;
74 0 : ctx->request = buildRequest(key);
75 0 : ctx->deviceName = std::move(deviceName);
76 0 : ctx->credentials = dynamic_unique_cast<ServerAccountCredentials>(std::move(credentials));
77 0 : ctx->onSuccess = std::move(onSuccess);
78 0 : ctx->onFailure = std::move(onFailure);
79 0 : if (not ctx->credentials or ctx->credentials->username.empty()) {
80 0 : ctx->onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
81 0 : return;
82 : }
83 :
84 0 : onChange_ = std::move(onChange);
85 0 : const std::string url = managerHostname_ + PATH_DEVICE;
86 0 : JAMI_WARNING("[Account {}] [Auth] Authentication with: {} to {}", accountId_, ctx->credentials->username, url);
87 :
88 0 : dht::ThreadPool::computation().run([ctx, url, w=weak_from_this()] {
89 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
90 0 : if (not this_) return;
91 0 : Json::Value body;
92 : {
93 0 : auto csr = ctx->request.get()->toString();
94 0 : body["csr"] = csr;
95 0 : body["deviceName"] = ctx->deviceName;
96 0 : }
97 : auto request = std::make_shared<Request>(
98 0 : *Manager::instance().ioContext(),
99 0 : url,
100 : body,
101 0 : [ctx, w](Json::Value json, const dht::http::Response& response) {
102 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
103 0 : JAMI_DEBUG("[Auth] Got request callback with status code={}",
104 : response.status_code);
105 0 : if (response.status_code == 0 || this_ == nullptr)
106 0 : ctx->onFailure(AuthError::SERVER_ERROR, "Unable to connect to server");
107 0 : else if (response.status_code >= 400 && response.status_code < 500)
108 0 : ctx->onFailure(AuthError::INVALID_ARGUMENTS, "Invalid credentials provided!");
109 0 : else if (response.status_code < 200 || response.status_code > 299)
110 0 : ctx->onFailure(AuthError::INVALID_ARGUMENTS, "");
111 : else {
112 : do {
113 : try {
114 0 : JAMI_WARNING("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
115 : auto cert = std::make_shared<dht::crypto::Certificate>(
116 0 : json["certificateChain"].asString());
117 0 : auto accountCert = cert->issuer;
118 0 : if (not accountCert) {
119 0 : JAMI_ERR("[Auth] Unable to parse certificate: no issuer");
120 0 : ctx->onFailure(AuthError::SERVER_ERROR,
121 : "Invalid certificate from server");
122 0 : break;
123 : }
124 0 : auto receipt = json["deviceReceipt"].asString();
125 0 : Json::Value receiptJson;
126 0 : std::string err;
127 : auto receiptReader = std::unique_ptr<Json::CharReader>(
128 0 : Json::CharReaderBuilder {}.newCharReader());
129 0 : if (!receiptReader->parse(receipt.data(),
130 0 : receipt.data() + receipt.size(),
131 : &receiptJson,
132 : &err)) {
133 0 : JAMI_ERR("[Auth] Unable to parse receipt from server: %s",
134 : err.c_str());
135 0 : ctx->onFailure(AuthError::SERVER_ERROR,
136 : "Unable to parse receipt from server");
137 0 : break;
138 : }
139 : auto receiptSignature = base64::decode(
140 0 : json["receiptSignature"].asString());
141 :
142 0 : auto info = std::make_unique<AccountInfo>();
143 0 : info->identity.first = ctx->key.get();
144 0 : info->identity.second = cert;
145 0 : info->devicePk = cert->getSharedPublicKey();
146 0 : info->deviceId = info->devicePk->getLongId().toString();
147 0 : info->accountId = accountCert->getId().toString();
148 0 : info->contacts = std::make_unique<ContactList>(ctx->accountId,
149 : accountCert,
150 0 : this_->path_,
151 0 : this_->onChange_);
152 : // info->contacts->setContacts(a.contacts);
153 0 : if (ctx->deviceName.empty())
154 0 : ctx->deviceName = info->deviceId.substr(8);
155 0 : info->contacts->foundAccountDevice(cert,
156 0 : ctx->deviceName,
157 0 : clock::now());
158 0 : info->ethAccount = receiptJson["eth"].asString();
159 0 : info->announce
160 0 : = parseAnnounce(receiptJson["announce"].asString(),
161 0 : info->accountId,
162 0 : info->devicePk->getId().toString(),
163 0 : info->devicePk->getLongId().toString());
164 0 : if (not info->announce) {
165 0 : ctx->onFailure(AuthError::SERVER_ERROR,
166 : "Unable to parse announce from server");
167 0 : return;
168 : }
169 0 : info->username = ctx->credentials->username;
170 :
171 0 : this_->creds_ = std::move(ctx->credentials);
172 0 : this_->info_ = std::move(info);
173 0 : std::map<std::string, std::string> config;
174 0 : for (auto itr = json.begin(); itr != json.end(); ++itr) {
175 0 : const auto& name = itr.name();
176 0 : if (name == "nameServer"sv) {
177 0 : auto nameServer = json["nameServer"].asString();
178 0 : if (!nameServer.empty() && nameServer[0] == '/')
179 0 : nameServer = this_->managerHostname_ + nameServer;
180 0 : this_->nameDir_ = NameDirectory::instance(nameServer);
181 : config
182 0 : .emplace(libjami::Account::ConfProperties::Nameserver::URI,
183 0 : std::move(nameServer));
184 0 : } else if (name == "userPhoto"sv) {
185 0 : this_->info_->photo = json["userPhoto"].asString();
186 : } else {
187 0 : config.emplace(name, itr->asString());
188 : }
189 0 : }
190 :
191 0 : ctx->onSuccess(*this_->info_,
192 0 : std::move(config),
193 0 : std::move(receipt),
194 0 : std::move(receiptSignature));
195 0 : } catch (const std::exception& e) {
196 0 : JAMI_ERROR("[Account {}] [Auth] Error when loading account: {}", this_->accountId_, e.what());
197 0 : ctx->onFailure(AuthError::NETWORK, "");
198 0 : }
199 : } while (false);
200 : }
201 :
202 0 : if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()))
203 0 : this_->clearRequest(response.request);
204 0 : },
205 0 : this_->logger_);
206 0 : request->set_auth(ctx->credentials->username, ctx->credentials->password);
207 0 : this_->sendRequest(request);
208 0 : });
209 0 : }
210 :
211 : void
212 0 : ServerAccountManager::onAuthEnded(const Json::Value& json,
213 : const dht::http::Response& response,
214 : TokenScope expectedScope)
215 : {
216 0 : if (response.status_code >= 200 && response.status_code < 300) {
217 0 : auto scopeStr = json["scope"].asString();
218 0 : auto scope = scopeStr == "DEVICE"sv
219 0 : ? TokenScope::Device
220 0 : : (scopeStr == "USER"sv ? TokenScope::User : TokenScope::None);
221 0 : auto expires_in = json["expires_in"].asLargestUInt();
222 0 : auto expiration = std::chrono::steady_clock::now() + std::chrono::seconds(expires_in);
223 0 : JAMI_WARNING("[Account {}] [Auth] Got server response: {} ({} bytes)", accountId_, response.status_code, response.body.size());
224 0 : setToken(json["access_token"].asString(), scope, expiration);
225 0 : } else {
226 0 : JAMI_WARNING("[Account {}] [Auth] Got server response: {} ({} bytes)", accountId_, response.status_code, response.body.size());
227 0 : authFailed(expectedScope, response.status_code);
228 : }
229 0 : clearRequest(response.request);
230 0 : }
231 :
232 : void
233 0 : ServerAccountManager::authenticateDevice()
234 : {
235 0 : if (not info_) {
236 0 : authFailed(TokenScope::Device, 0);
237 : }
238 0 : const std::string url = managerHostname_ + JAMI_PATH_LOGIN;
239 0 : JAMI_WARNING("[Account {}] [Auth] Getting a device token: {}", accountId_, url);
240 : auto request = std::make_shared<Request>(
241 0 : *Manager::instance().ioContext(),
242 : url,
243 0 : Json::Value {Json::objectValue},
244 0 : [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
245 0 : if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()))
246 0 : this_->onAuthEnded(json, response, TokenScope::Device);
247 0 : },
248 0 : logger_);
249 0 : request->set_identity(info_->identity);
250 : // request->set_certificate_authority(info_->identity.second->issuer->issuer);
251 0 : sendRequest(request);
252 0 : }
253 :
254 : void
255 0 : ServerAccountManager::authenticateAccount(const std::string& username, const std::string& password)
256 : {
257 0 : const std::string url = managerHostname_ + JAMI_PATH_LOGIN;
258 0 : JAMI_WARNING("[Account {}] [Auth] Getting a user token: {}", accountId_, url);
259 : auto request = std::make_shared<Request>(
260 0 : *Manager::instance().ioContext(),
261 : url,
262 0 : Json::Value {Json::objectValue},
263 0 : [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
264 0 : if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()))
265 0 : this_->onAuthEnded(json, response, TokenScope::User);
266 0 : },
267 0 : logger_);
268 0 : request->set_auth(username, password);
269 0 : sendRequest(request);
270 0 : }
271 :
272 : void
273 0 : ServerAccountManager::sendRequest(const std::shared_ptr<dht::http::Request>& request)
274 : {
275 0 : request->set_header_field(restinio::http_field_t::user_agent, userAgent());
276 : {
277 0 : std::lock_guard lock(requestLock_);
278 0 : requests_.emplace(request);
279 0 : }
280 0 : request->send();
281 0 : }
282 :
283 : void
284 0 : ServerAccountManager::clearRequest(const std::weak_ptr<dht::http::Request>& request)
285 : {
286 0 : asio::post(*Manager::instance().ioContext(), [w=weak_from_this(), request] {
287 0 : if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock())) {
288 0 : if (auto req = request.lock()) {
289 0 : std::lock_guard lock(this_->requestLock_);
290 0 : this_->requests_.erase(req);
291 0 : }
292 0 : }
293 0 : });
294 0 : }
295 :
296 : void
297 0 : ServerAccountManager::authFailed(TokenScope scope, int code)
298 : {
299 0 : RequestQueue requests;
300 : {
301 0 : std::lock_guard lock(tokenLock_);
302 0 : requests = std::move(getRequestQueue(scope));
303 0 : }
304 0 : JAMI_DEBUG("[Auth] Failed auth with scope {}, ending {} pending requests",
305 : (int) scope,
306 : requests.size());
307 0 : if (code == 401) {
308 : // NOTE: we do not login every time to the server but retrieve a device token to use the account
309 : // If authentificate device fails with 401
310 : // it means that the device is revoked
311 0 : if (onNeedsMigration_)
312 0 : onNeedsMigration_();
313 : }
314 0 : while (not requests.empty()) {
315 0 : auto req = std::move(requests.front());
316 0 : requests.pop();
317 0 : req->terminate(code == 0 ? asio::error::not_connected : asio::error::access_denied);
318 0 : }
319 0 : }
320 :
321 : void
322 0 : ServerAccountManager::authError(TokenScope scope)
323 : {
324 : {
325 0 : std::lock_guard lock(tokenLock_);
326 0 : if (scope <= tokenScope_) {
327 0 : token_ = {};
328 0 : tokenScope_ = TokenScope::None;
329 : }
330 0 : }
331 0 : if (scope == TokenScope::Device)
332 0 : authenticateDevice();
333 0 : }
334 :
335 : void
336 0 : ServerAccountManager::setToken(std::string token,
337 : TokenScope scope,
338 : std::chrono::steady_clock::time_point expiration)
339 : {
340 0 : std::lock_guard lock(tokenLock_);
341 0 : token_ = std::move(token);
342 0 : tokenScope_ = scope;
343 0 : tokenExpire_ = expiration;
344 :
345 0 : nameDir_.get().setToken(token_);
346 0 : if (not token_.empty() and scope != TokenScope::None) {
347 0 : auto& reqQueue = getRequestQueue(scope);
348 0 : JAMI_DEBUG("[Account {}] [Auth] Got token with scope {}, handling {} pending requests",
349 : accountId_,
350 : (int) scope,
351 : reqQueue.size());
352 0 : while (not reqQueue.empty()) {
353 0 : auto req = std::move(reqQueue.front());
354 0 : reqQueue.pop();
355 0 : setAuthHeaderFields(*req);
356 0 : sendRequest(req);
357 0 : }
358 : }
359 0 : }
360 :
361 : void
362 0 : ServerAccountManager::sendDeviceRequest(const std::shared_ptr<dht::http::Request>& req)
363 : {
364 0 : std::lock_guard lock(tokenLock_);
365 0 : if (hasAuthorization(TokenScope::Device)) {
366 0 : setAuthHeaderFields(*req);
367 0 : sendRequest(req);
368 : } else {
369 0 : auto& rQueue = getRequestQueue(TokenScope::Device);
370 0 : if (rQueue.empty())
371 0 : authenticateDevice();
372 0 : rQueue.emplace(req);
373 : }
374 0 : }
375 :
376 : void
377 0 : ServerAccountManager::sendAccountRequest(const std::shared_ptr<dht::http::Request>& req,
378 : const std::string& pwd)
379 : {
380 0 : std::lock_guard lock(tokenLock_);
381 0 : if (hasAuthorization(TokenScope::User)) {
382 0 : setAuthHeaderFields(*req);
383 0 : sendRequest(req);
384 : } else {
385 0 : auto& rQueue = getRequestQueue(TokenScope::User);
386 0 : if (rQueue.empty())
387 0 : authenticateAccount(info_->username, pwd);
388 0 : rQueue.emplace(req);
389 : }
390 0 : }
391 :
392 : void
393 0 : ServerAccountManager::syncDevices()
394 : {
395 0 : const std::string urlDevices = managerHostname_ + PATH_DEVICES;
396 0 : const std::string urlContacts = managerHostname_ + PATH_CONTACTS;
397 0 : const std::string urlConversations = managerHostname_ + PATH_CONVERSATIONS;
398 0 : const std::string urlConversationsRequests = managerHostname_ + PATH_CONVERSATIONS_REQUESTS;
399 :
400 0 : JAMI_WARNING("[Account {}] [Auth] Sync conversations {}", accountId_, urlConversations);
401 0 : Json::Value jsonConversations(Json::arrayValue);
402 0 : for (const auto& [key, convInfo] : ConversationModule::convInfos(accountId_)) {
403 0 : jsonConversations.append(convInfo.toJson());
404 0 : }
405 0 : sendDeviceRequest(std::make_shared<Request>(
406 0 : *Manager::instance().ioContext(),
407 : urlConversations,
408 : jsonConversations,
409 0 : [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
410 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
411 0 : if (!this_) return;
412 0 : JAMI_DEBUG("[Account {}] [Auth] Got conversation sync request callback with status code={}",
413 : this_->accountId_,
414 : response.status_code);
415 0 : if (response.status_code >= 200 && response.status_code < 300) {
416 : try {
417 0 : JAMI_WARNING("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
418 0 : if (not json.isArray()) {
419 0 : JAMI_ERROR("[Account {}] [Auth] Unable to parse server response: not an array", this_->accountId_);
420 : } else {
421 0 : SyncMsg convs;
422 0 : for (unsigned i = 0, n = json.size(); i < n; i++) {
423 0 : const auto& e = json[i];
424 0 : auto ci = ConvInfo(e);
425 0 : convs.c[ci.id] = std::move(ci);
426 0 : }
427 0 : dht::ThreadPool::io().run([accountId=this_->accountId_, convs] {
428 0 : if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) {
429 0 : acc->convModule()->onSyncData(convs, "", "");
430 0 : }
431 0 : });
432 0 : }
433 0 : } catch (const std::exception& e) {
434 0 : JAMI_ERROR("[Account {}] Error when iterating conversation list: {}", this_->accountId_, e.what());
435 0 : }
436 0 : } else if (response.status_code == 401)
437 0 : this_->authError(TokenScope::Device);
438 :
439 0 : this_->clearRequest(response.request);
440 0 : },
441 0 : logger_));
442 :
443 0 : JAMI_WARNING("[Account {}] [Auth] Sync conversations requests {}", accountId_, urlConversationsRequests);
444 0 : Json::Value jsonConversationsRequests(Json::arrayValue);
445 0 : for (const auto& [key, convRequest] : ConversationModule::convRequests(accountId_)) {
446 0 : auto jsonConversation = convRequest.toJson();
447 0 : jsonConversationsRequests.append(std::move(jsonConversation));
448 0 : }
449 0 : sendDeviceRequest(std::make_shared<Request>(
450 0 : *Manager::instance().ioContext(),
451 : urlConversationsRequests,
452 : jsonConversationsRequests,
453 0 : [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
454 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
455 0 : if (!this_) return;
456 0 : JAMI_DEBUG("[Account {}] [Auth] Got conversations requests sync request callback with status code={}",
457 : this_->accountId_, response.status_code);
458 0 : if (response.status_code >= 200 && response.status_code < 300) {
459 : try {
460 0 : JAMI_WARNING("[Account {}] [Auth] Got server response: {}", this_->accountId_, response.body);
461 0 : if (not json.isArray()) {
462 0 : JAMI_ERROR("[Account {}] [Auth] Unable to parse server response: not an array", this_->accountId_);
463 : } else {
464 0 : SyncMsg convReqs;
465 0 : for (unsigned i = 0, n = json.size(); i < n; i++) {
466 0 : const auto& e = json[i];
467 0 : auto cr = ConversationRequest(e);
468 0 : convReqs.cr[cr.conversationId] = std::move(cr);
469 0 : }
470 0 : dht::ThreadPool::io().run([accountId=this_->accountId_, convReqs] {
471 0 : if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) {
472 0 : acc->convModule()->onSyncData(convReqs, "", "");
473 0 : }
474 0 : });
475 0 : }
476 0 : } catch (const std::exception& e) {
477 0 : JAMI_ERROR("[Account {}] Error when iterating conversations requests list: {}", this_->accountId_, e.what());
478 0 : }
479 0 : } else if (response.status_code == 401)
480 0 : this_->authError(TokenScope::Device);
481 :
482 0 : this_->clearRequest(response.request);
483 0 : },
484 0 : logger_));
485 :
486 0 : JAMI_WARNING("[Account {}] [Auth] syncContacts {}", accountId_, urlContacts);
487 0 : Json::Value jsonContacts(Json::arrayValue);
488 0 : for (const auto& contact : info_->contacts->getContacts()) {
489 0 : auto jsonContact = contact.second.toJson();
490 0 : jsonContact["uri"] = contact.first.toString();
491 0 : jsonContacts.append(std::move(jsonContact));
492 0 : }
493 0 : sendDeviceRequest(std::make_shared<Request>(
494 0 : *Manager::instance().ioContext(),
495 : urlContacts,
496 : jsonContacts,
497 0 : [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
498 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
499 0 : if (!this_) return;
500 0 : JAMI_DEBUG("[Account {}] [Auth] Got contact sync request callback with status code={}",
501 : this_->accountId_, response.status_code);
502 0 : if (response.status_code >= 200 && response.status_code < 300) {
503 : try {
504 0 : JAMI_WARNING("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
505 0 : if (not json.isArray()) {
506 0 : JAMI_ERROR("[Auth] Unable to parse server response: not an array");
507 : } else {
508 0 : for (unsigned i = 0, n = json.size(); i < n; i++) {
509 0 : const auto& e = json[i];
510 0 : Contact contact(e);
511 0 : this_->info_->contacts
512 0 : ->updateContact(dht::InfoHash {e["uri"].asString()}, contact);
513 0 : }
514 0 : this_->info_->contacts->saveContacts();
515 : }
516 0 : } catch (const std::exception& e) {
517 0 : JAMI_ERROR("Error when iterating contact list: {}", e.what());
518 0 : }
519 0 : } else if (response.status_code == 401)
520 0 : this_->authError(TokenScope::Device);
521 :
522 0 : this_->clearRequest(response.request);
523 0 : },
524 0 : logger_));
525 :
526 0 : JAMI_WARNING("[Account {}] [Auth] syncDevices {}", accountId_, urlDevices);
527 0 : sendDeviceRequest(std::make_shared<Request>(
528 0 : *Manager::instance().ioContext(),
529 : urlDevices,
530 0 : [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
531 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
532 0 : if (!this_) return;
533 0 : JAMI_DEBUG("[Account {}] [Auth] Got request callback with status code={}", this_->accountId_, response.status_code);
534 0 : if (response.status_code >= 200 && response.status_code < 300) {
535 : try {
536 0 : JAMI_LOG("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
537 0 : if (not json.isArray()) {
538 0 : JAMI_ERROR("[Auth] Unable to parse server response: not an array");
539 : } else {
540 0 : for (unsigned i = 0, n = json.size(); i < n; i++) {
541 0 : const auto& e = json[i];
542 0 : const bool revoked = e["revoked"].asBool();
543 0 : dht::PkId deviceId(e["deviceId"].asString());
544 0 : if(!deviceId){
545 0 : continue;
546 : }
547 0 : if (!revoked) {
548 0 : this_->info_->contacts->foundAccountDevice(deviceId,
549 0 : e["alias"].asString(),
550 0 : clock::now());
551 : }
552 : else {
553 0 : this_->info_->contacts->removeAccountDevice(deviceId);
554 : }
555 : }
556 : }
557 0 : } catch (const std::exception& e) {
558 0 : JAMI_ERROR("Error when iterating device list: {}", e.what());
559 0 : }
560 0 : } else if (response.status_code == 401)
561 0 : this_->authError(TokenScope::Device);
562 :
563 0 : this_->clearRequest(response.request);
564 0 : },
565 0 : logger_));
566 0 : }
567 :
568 : void
569 0 : ServerAccountManager::syncBlueprintConfig(SyncBlueprintCallback onSuccess)
570 : {
571 0 : auto syncBlueprintCallback = std::make_shared<SyncBlueprintCallback>(onSuccess);
572 0 : const std::string urlBlueprints = managerHostname_ + PATH_BLUEPRINT;
573 0 : JAMI_DEBUG("[Auth] Synchronize blueprint configuration {}", urlBlueprints);
574 0 : sendDeviceRequest(std::make_shared<Request>(
575 0 : *Manager::instance().ioContext(),
576 : urlBlueprints,
577 0 : [syncBlueprintCallback, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
578 0 : JAMI_DEBUG("[Auth] Got sync request callback with status code={}", response.status_code);
579 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
580 0 : if (!this_) return;
581 0 : if (response.status_code >= 200 && response.status_code < 300) {
582 : try {
583 0 : std::map<std::string, std::string> config;
584 0 : for (auto itr = json.begin(); itr != json.end(); ++itr) {
585 0 : const auto& name = itr.name();
586 0 : config.emplace(name, itr->asString());
587 0 : }
588 0 : (*syncBlueprintCallback)(config);
589 0 : } catch (const std::exception& e) {
590 0 : JAMI_ERROR("Error when iterating blueprint config json: {}", e.what());
591 0 : }
592 0 : } else if (response.status_code == 401)
593 0 : this_->authError(TokenScope::Device);
594 0 : this_->clearRequest(response.request);
595 0 : },
596 0 : logger_));
597 0 : }
598 :
599 : bool
600 0 : ServerAccountManager::revokeDevice(const std::string& device,
601 : std::string_view scheme, const std::string& password,
602 : RevokeDeviceCallback cb)
603 : {
604 0 : if (not info_ || scheme != fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD) {
605 0 : if (cb)
606 0 : cb(RevokeDeviceResult::ERROR_CREDENTIALS);
607 0 : return false;
608 : }
609 0 : const std::string url = managerHostname_ + PATH_DEVICE + "/" + device;
610 0 : JAMI_WARNING("[Revoke] Revoking device of {} at {}", info_->username, url);
611 : auto request = std::make_shared<Request>(
612 0 : *Manager::instance().ioContext(),
613 : url,
614 0 : [cb, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
615 0 : JAMI_DEBUG("[Revoke] Got request callback with status code={}", response.status_code);
616 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
617 0 : if (!this_) return;
618 0 : if (response.status_code >= 200 && response.status_code < 300) {
619 : try {
620 0 : JAMI_WARNING("[Revoke] Got server response");
621 0 : if (json["errorDetails"].empty()) {
622 0 : if (cb)
623 0 : cb(RevokeDeviceResult::SUCCESS);
624 0 : this_->syncDevices(); // this will remove the devices from the known devices
625 : }
626 0 : } catch (const std::exception& e) {
627 0 : JAMI_ERROR("Error when loading device list: {}", e.what());
628 0 : }
629 0 : } else if (cb)
630 0 : cb(RevokeDeviceResult::ERROR_NETWORK);
631 0 : this_->clearRequest(response.request);
632 0 : },
633 0 : logger_);
634 0 : request->set_method(restinio::http_method_delete());
635 0 : sendAccountRequest(request, password);
636 0 : return true;
637 0 : }
638 :
639 : void
640 0 : ServerAccountManager::registerName(const std::string& name, std::string_view scheme, const std::string&, RegistrationCallback cb)
641 : {
642 0 : cb(NameDirectory::RegistrationResponse::unsupported, name);
643 0 : }
644 :
645 : bool
646 0 : ServerAccountManager::searchUser(const std::string& query, SearchCallback cb)
647 : {
648 0 : const std::string url = managerHostname_ + PATH_SEARCH + "?queryString=" + query;
649 0 : JAMI_WARNING("[Search] Searching user {} at {}", query, url);
650 0 : sendDeviceRequest(std::make_shared<Request>(
651 0 : *Manager::instance().ioContext(),
652 : url,
653 0 : [cb, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
654 0 : JAMI_DEBUG("[Search] Got request callback with status code={}", response.status_code);
655 0 : auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
656 0 : if (!this_) return;
657 0 : if (response.status_code >= 200 && response.status_code < 300) {
658 : try {
659 0 : const auto& profiles = json["profiles"];
660 0 : Json::Value::ArrayIndex rcount = profiles.size();
661 0 : std::vector<std::map<std::string, std::string>> results;
662 0 : results.reserve(rcount);
663 0 : JAMI_WARNING("[Search] Got server response: {} bytes", response.body.size());
664 0 : for (Json::Value::ArrayIndex i = 0; i < rcount; i++) {
665 0 : const auto& ruser = profiles[i];
666 0 : std::map<std::string, std::string> user;
667 0 : for (const auto& member : ruser.getMemberNames()) {
668 0 : const auto& rmember = ruser[member];
669 0 : if (rmember.isString())
670 0 : user[member] = rmember.asString();
671 0 : }
672 0 : results.emplace_back(std::move(user));
673 0 : }
674 0 : if (cb)
675 0 : cb(results, SearchResponse::found);
676 0 : } catch (const std::exception& e) {
677 0 : JAMI_ERROR("[Search] Error during search: {}", e.what());
678 0 : }
679 0 : } else {
680 0 : if (response.status_code == 401)
681 0 : this_->authError(TokenScope::Device);
682 0 : if (cb)
683 0 : cb({}, SearchResponse::error);
684 : }
685 0 : this_->clearRequest(response.request);
686 0 : },
687 0 : logger_));
688 0 : return true;
689 0 : }
690 :
691 : } // namespace jami
|