LCOV - code coverage report
Current view: top level - src/jamidht - server_account_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 0.0 % 489 0
Test Date: 2026-06-13 09:18:46 Functions: 0.0 % 123 0

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

Generated by: LCOV version 2.0-1