LCOV - code coverage report
Current view: top level - src/jamidht - server_account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 0 463 0.0 %
Date: 2024-12-21 08:56:24 Functions: 0 101 0.0 %

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

Generated by: LCOV version 1.14