LCOV - code coverage report
Current view: top level - foo/src/jamidht - server_account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 0 479 0.0 %
Date: 2025-12-18 10:07:43 Functions: 0 118 0.0 %

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

Generated by: LCOV version 1.14