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 472 0.0 %
Date: 2025-08-24 09:11:10 Functions: 0 114 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             : 
      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

Generated by: LCOV version 1.14