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 :
18 : #include "jamipluginmanager.h"
19 : #include "pluginsutils.h"
20 : #include "fileutils.h"
21 : #include "archiver.h"
22 : #include "logger.h"
23 : #include "manager.h"
24 : #include "jamidht/jamiaccount.h"
25 : #include "jami/plugin_manager_interface.h"
26 : // NOLINTNEXTLINE
27 : #include "store_ca_crt.cpp"
28 :
29 : #include <fstream>
30 : #include <msgpack.hpp>
31 :
32 : #define FAILURE -1
33 : #define SUCCESS 0
34 : #define PLUGIN_ALREADY_INSTALLED 100 /* Plugin already installed with the same version */
35 : #define PLUGIN_OLD_VERSION 200 /* Plugin already installed with a newer version */
36 : #define SIGNATURE_VERIFICATION_FAILED 300
37 : #define CERTIFICATE_VERIFICATION_FAILED 400
38 : #define INVALID_PLUGIN 500
39 :
40 : #ifdef WIN32
41 : #define LIB_TYPE ".dll"
42 : #define LIB_PREFIX ""
43 : #else
44 : #ifdef __APPLE__
45 : #define LIB_TYPE ".dylib"
46 : #define LIB_PREFIX "lib"
47 : #else
48 : #define LIB_TYPE ".so"
49 : #define LIB_PREFIX "lib"
50 : #endif
51 : #endif
52 :
53 : namespace jami {
54 :
55 33 : JamiPluginManager::JamiPluginManager()
56 33 : : callsm_ {pm_}
57 33 : , chatsm_ {pm_}
58 33 : , webviewsm_ {pm_}
59 66 : , preferencesm_ {pm_}
60 : {
61 33 : registerServices();
62 33 : }
63 :
64 : std::string
65 10 : JamiPluginManager::getPluginAuthor(const std::string& rootPath, const std::string& pluginId)
66 : {
67 10 : auto cert = PluginUtils::readPluginCertificate(rootPath, pluginId);
68 10 : if (!cert) {
69 0 : JAMI_ERROR("Unable to read plugin certificate");
70 0 : return {};
71 : }
72 10 : return cert->getIssuerName();
73 10 : }
74 :
75 : std::map<std::string, std::string>
76 19 : JamiPluginManager::getPluginDetails(const std::string& rootPath, bool reset)
77 : {
78 19 : auto detailsIt = pluginDetailsMap_.find(rootPath);
79 19 : if (detailsIt != pluginDetailsMap_.end()) {
80 12 : if (!reset)
81 9 : return detailsIt->second;
82 3 : pluginDetailsMap_.erase(detailsIt);
83 : }
84 :
85 20 : std::map<std::string, std::string> details = PluginUtils::parseManifestFile(PluginUtils::manifestPath(rootPath),
86 10 : rootPath);
87 10 : if (!details.empty()) {
88 10 : auto itIcon = details.find("iconPath");
89 10 : itIcon->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
90 :
91 10 : auto itImage = details.find("backgroundPath");
92 10 : itImage->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
93 :
94 40 : details["soPath"] = rootPath + DIR_SEPARATOR_CH + LIB_PREFIX + details["id"] + LIB_TYPE;
95 30 : details["author"] = getPluginAuthor(rootPath, details["id"]);
96 10 : detailsIt = pluginDetailsMap_.emplace(rootPath, std::move(details)).first;
97 10 : return detailsIt->second;
98 : }
99 0 : return {};
100 10 : }
101 :
102 : std::vector<std::string>
103 2 : JamiPluginManager::getInstalledPlugins()
104 : {
105 : // Gets all plugins in standard path
106 2 : auto pluginsPath = fileutils::get_data_dir() / "plugins";
107 2 : std::vector<std::string> pluginsPaths;
108 2 : std::error_code ec;
109 4 : for (const auto& entry : std::filesystem::directory_iterator(pluginsPath, ec)) {
110 1 : const auto& p = entry.path();
111 1 : if (PluginUtils::checkPluginValidity(p))
112 1 : pluginsPaths.emplace_back(p.string());
113 2 : }
114 :
115 : // Gets plugins installed in non standard path
116 2 : std::vector<std::string> nonStandardInstalls = jami::Manager::instance().pluginPreferences.getInstalledPlugins();
117 2 : for (auto& path : nonStandardInstalls) {
118 0 : if (PluginUtils::checkPluginValidity(path))
119 0 : pluginsPaths.emplace_back(path);
120 : }
121 :
122 4 : return pluginsPaths;
123 2 : }
124 :
125 : bool
126 0 : JamiPluginManager::checkPluginCertificatePublicKey(const std::string& oldJplPath, const std::string& newJplPath)
127 : {
128 0 : std::map<std::string, std::string> oldDetails = PluginUtils::parseManifestFile(PluginUtils::manifestPath(oldJplPath),
129 0 : oldJplPath);
130 0 : std::error_code ec;
131 0 : if (oldDetails.empty()
132 0 : || !std::filesystem::is_regular_file(oldJplPath + DIR_SEPARATOR_CH + oldDetails["id"] + ".crt", ec)
133 0 : || !std::filesystem::is_regular_file(newJplPath, ec))
134 0 : return false;
135 : try {
136 0 : auto oldCert = PluginUtils::readPluginCertificate(oldJplPath, oldDetails["id"]);
137 0 : auto newCert = PluginUtils::readPluginCertificateFromArchive(newJplPath);
138 0 : if (!oldCert || !newCert) {
139 0 : return false;
140 : }
141 0 : return oldCert->getPublicKey() == newCert->getPublicKey();
142 0 : } catch (const std::exception& e) {
143 0 : JAMI_ERROR("{}", e.what());
144 0 : return false;
145 0 : }
146 : return true;
147 0 : }
148 :
149 : bool
150 12 : JamiPluginManager::checkPluginCertificateValidity(dht::crypto::Certificate* cert)
151 : {
152 12 : if (!cert || !*cert)
153 0 : return false;
154 12 : trust_.add(crypto::Certificate(store_ca_crt, sizeof(store_ca_crt)));
155 12 : auto result = trust_.verify(*cert);
156 12 : if (!result) {
157 12 : JAMI_ERROR("Certificate verification failed: {}", result.toString());
158 : }
159 12 : return (bool) result;
160 : }
161 :
162 : std::map<std::string, std::string>
163 0 : JamiPluginManager::getPlatformInfo()
164 : {
165 0 : return PluginUtils::getPlatformInfo();
166 : }
167 :
168 : bool
169 12 : JamiPluginManager::checkPluginSignatureFile(const std::string& jplPath)
170 : {
171 : // check if the file exists
172 12 : std::error_code ec;
173 12 : if (!std::filesystem::is_regular_file(jplPath, ec)) {
174 1 : return false;
175 : }
176 : try {
177 11 : auto signatures = PluginUtils::readPluginSignatureFromArchive(jplPath);
178 11 : auto manifest = PluginUtils::readPluginManifestFromArchive(jplPath);
179 11 : const std::string& name = manifest["id"];
180 11 : auto filesPath = archiver::listFilesFromArchive(jplPath);
181 156 : for (const auto& file : filesPath) {
182 : // we skip the signatures and signatures.sig file
183 147 : if (file == "signatures" || file == "signatures.sig")
184 20 : continue;
185 : // we also skip the plugin certificate
186 127 : if (file == name + ".crt")
187 9 : continue;
188 :
189 118 : if (signatures.count(file) == 0) {
190 2 : return false;
191 : }
192 : }
193 15 : } catch (const std::exception& e) {
194 0 : return false;
195 0 : }
196 9 : return true;
197 : }
198 :
199 : bool
200 11 : JamiPluginManager::checkPluginSignatureValidity(const std::string& jplPath, dht::crypto::Certificate* cert)
201 : {
202 11 : if (!std::filesystem::is_regular_file(jplPath))
203 0 : return false;
204 : try {
205 11 : const auto& pk = cert->getPublicKey();
206 10 : auto signaturesData = archiver::readFileFromArchive(jplPath, "signatures");
207 10 : auto signatureFile = PluginUtils::readSignatureFileFromArchive(jplPath);
208 10 : if (!pk.checkSignature(signaturesData, signatureFile))
209 1 : return false;
210 9 : auto signatures = PluginUtils::readPluginSignatureFromArchive(jplPath);
211 117 : for (const auto& signature : signatures) {
212 108 : auto file = archiver::readFileFromArchive(jplPath, signature.first);
213 108 : if (!pk.checkSignature(file, signature.second)) {
214 0 : JAMI_ERROR("{} not correctly signed", signature.first);
215 0 : return false;
216 : }
217 108 : }
218 12 : } catch (const std::exception& e) {
219 1 : return false;
220 1 : }
221 :
222 9 : return true;
223 : }
224 :
225 : bool
226 9 : JamiPluginManager::checkPluginSignature(const std::string& jplPath, dht::crypto::Certificate* cert)
227 : {
228 9 : if (!std::filesystem::is_regular_file(jplPath) || !cert || !*cert)
229 1 : return false;
230 : try {
231 8 : return checkPluginSignatureValidity(jplPath, cert) && checkPluginSignatureFile(jplPath);
232 0 : } catch (const std::exception& e) {
233 0 : return false;
234 0 : }
235 : }
236 :
237 : std::unique_ptr<dht::crypto::Certificate>
238 13 : JamiPluginManager::checkPluginCertificate(const std::string& jplPath, bool force)
239 : {
240 13 : if (!std::filesystem::is_regular_file(jplPath))
241 1 : return {};
242 : try {
243 12 : auto cert = PluginUtils::readPluginCertificateFromArchive(jplPath);
244 12 : if (checkPluginCertificateValidity(cert.get()) || force) {
245 10 : return cert;
246 : }
247 2 : return {};
248 12 : } catch (const std::exception& e) {
249 0 : return {};
250 0 : }
251 : }
252 :
253 : int
254 7 : JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
255 : {
256 7 : int r {SUCCESS};
257 7 : std::error_code ec;
258 7 : if (std::filesystem::is_regular_file(jplPath, ec)) {
259 : try {
260 7 : auto manifestMap = PluginUtils::readPluginManifestFromArchive(jplPath);
261 7 : const std::string& name = manifestMap["id"];
262 7 : if (name.empty())
263 0 : return INVALID_PLUGIN;
264 7 : auto cert = checkPluginCertificate(jplPath, force);
265 7 : if (!cert)
266 0 : return CERTIFICATE_VERIFICATION_FAILED;
267 7 : if (!checkPluginSignature(jplPath, cert.get()))
268 0 : return SIGNATURE_VERIFICATION_FAILED;
269 7 : const std::string& version = manifestMap["version"];
270 7 : auto destinationDir = (fileutils::get_data_dir() / "plugins" / name).string();
271 : // Find if there is an existing version of this plugin
272 14 : const auto alreadyInstalledManifestMap = PluginUtils::parseManifestFile(PluginUtils::manifestPath(
273 : destinationDir),
274 7 : destinationDir);
275 :
276 7 : if (!alreadyInstalledManifestMap.empty()) {
277 2 : if (force) {
278 2 : r = uninstallPlugin(destinationDir);
279 2 : if (r == SUCCESS) {
280 2 : archiver::uncompressArchive(jplPath, destinationDir, PluginUtils::uncompressJplFunction);
281 : }
282 : } else {
283 0 : std::string installedVersion = alreadyInstalledManifestMap.at("version");
284 0 : if (version > installedVersion) {
285 0 : if (!checkPluginCertificatePublicKey(destinationDir, jplPath))
286 0 : return CERTIFICATE_VERIFICATION_FAILED;
287 0 : r = uninstallPlugin(destinationDir);
288 0 : if (r == SUCCESS) {
289 0 : archiver::uncompressArchive(jplPath, destinationDir, PluginUtils::uncompressJplFunction);
290 : }
291 0 : } else if (version == installedVersion) {
292 0 : r = PLUGIN_ALREADY_INSTALLED;
293 : } else {
294 0 : r = PLUGIN_OLD_VERSION;
295 : }
296 0 : }
297 : } else {
298 5 : archiver::uncompressArchive(jplPath, destinationDir, PluginUtils::uncompressJplFunction);
299 : }
300 7 : if (!libjami::getPluginsEnabled()) {
301 0 : libjami::setPluginsEnabled(true);
302 0 : Manager::instance().saveConfig();
303 0 : loadPlugins();
304 0 : return r;
305 : }
306 7 : libjami::loadPlugin(destinationDir);
307 7 : } catch (const std::exception& e) {
308 0 : JAMI_ERROR("{}", e.what());
309 0 : }
310 : }
311 7 : return r;
312 : }
313 :
314 : int
315 7 : JamiPluginManager::uninstallPlugin(const std::string& rootPath)
316 : {
317 7 : std::error_code ec;
318 7 : if (PluginUtils::checkPluginValidity(rootPath)) {
319 7 : auto detailsIt = pluginDetailsMap_.find(rootPath);
320 7 : if (detailsIt != pluginDetailsMap_.end()) {
321 7 : bool loaded = pm_.checkLoadedPlugin(rootPath);
322 7 : if (loaded) {
323 20 : JAMI_LOG("PLUGIN: unloading before uninstall.");
324 5 : bool status = libjami::unloadPlugin(rootPath);
325 5 : if (!status) {
326 0 : JAMI_LOG("PLUGIN: unable to unload, not performing uninstall.");
327 0 : return FAILURE;
328 : }
329 : }
330 21 : for (const auto& accId : jami::Manager::instance().getAccountList())
331 42 : std::filesystem::remove_all(fileutils::get_data_dir() / accId / "plugins" / detailsIt->second.at("id"),
332 7 : ec);
333 7 : pluginDetailsMap_.erase(detailsIt);
334 : }
335 7 : return std::filesystem::remove_all(rootPath, ec) ? SUCCESS : FAILURE;
336 : } else {
337 0 : JAMI_LOG("PLUGIN: not installed.");
338 0 : return FAILURE;
339 : }
340 : }
341 :
342 : bool
343 8 : JamiPluginManager::loadPlugin(const std::string& rootPath)
344 : {
345 : #ifdef ENABLE_PLUGIN
346 : try {
347 24 : bool status = pm_.load(getPluginDetails(rootPath).at("soPath"));
348 32 : JAMI_LOG("PLUGIN: load status - {}", status);
349 :
350 8 : return status;
351 :
352 0 : } catch (const std::exception& e) {
353 0 : JAMI_ERROR("{}", e.what());
354 0 : return false;
355 0 : }
356 : #endif
357 : return false;
358 : }
359 :
360 : bool
361 10 : JamiPluginManager::loadPlugins()
362 : {
363 : #ifdef ENABLE_PLUGIN
364 10 : bool status = true;
365 10 : auto loadedPlugins = jami::Manager::instance().pluginPreferences.getLoadedPlugins();
366 11 : for (const auto& pluginPath : loadedPlugins) {
367 1 : status &= loadPlugin(pluginPath);
368 : }
369 10 : return status;
370 : #endif
371 : return false;
372 10 : }
373 :
374 : bool
375 7 : JamiPluginManager::unloadPlugin(const std::string& rootPath)
376 : {
377 : #ifdef ENABLE_PLUGIN
378 : try {
379 21 : bool status = pm_.unload(getPluginDetails(rootPath).at("soPath"));
380 28 : JAMI_LOG("PLUGIN: unload status - {}", status);
381 :
382 7 : return status;
383 0 : } catch (const std::exception& e) {
384 0 : JAMI_ERROR("{}", e.what());
385 0 : return false;
386 0 : }
387 : #endif
388 : return false;
389 : }
390 :
391 : std::vector<std::string>
392 4 : JamiPluginManager::getLoadedPlugins() const
393 : {
394 4 : std::vector<std::string> loadedSoPlugins = pm_.getLoadedPlugins();
395 4 : std::vector<std::string> loadedPlugins {};
396 4 : loadedPlugins.reserve(loadedSoPlugins.size());
397 4 : std::transform(loadedSoPlugins.begin(),
398 : loadedSoPlugins.end(),
399 : std::back_inserter(loadedPlugins),
400 2 : [](const std::string& soPath) { return PluginUtils::getRootPathFromSoPath(soPath).string(); });
401 8 : return loadedPlugins;
402 4 : }
403 :
404 : std::vector<std::map<std::string, std::string>>
405 2 : JamiPluginManager::getPluginPreferences(const std::string& rootPath, const std::string& accountId)
406 : {
407 2 : return PluginPreferencesUtils::getPreferences(rootPath, accountId);
408 : }
409 :
410 : bool
411 2 : JamiPluginManager::setPluginPreference(const std::filesystem::path& rootPath,
412 : const std::string& accountId,
413 : const std::string& key,
414 : const std::string& value)
415 : {
416 2 : std::string acc = accountId;
417 :
418 : // If we try to change a preference value linked to an account
419 : // but that preference is global, we must ignore accountId and
420 : // change the preference for every account
421 2 : if (!accountId.empty()) {
422 : // Get global preferences
423 1 : auto preferences = PluginPreferencesUtils::getPreferences(rootPath, "");
424 : // Check if the preference we want to change is global
425 1 : auto it = std::find_if(preferences.cbegin(),
426 : preferences.cend(),
427 2 : [key](const std::map<std::string, std::string>& preference) {
428 3 : return preference.at("key") == key;
429 : });
430 : // Ignore accountId if global preference
431 1 : if (it != preferences.cend())
432 0 : acc.clear();
433 1 : }
434 :
435 : std::map<std::string, std::string> pluginUserPreferencesMap
436 2 : = PluginPreferencesUtils::getUserPreferencesValuesMap(rootPath, acc);
437 : std::map<std::string, std::string> pluginPreferencesMap = PluginPreferencesUtils::getPreferencesValuesMap(rootPath,
438 2 : acc);
439 :
440 : // If any plugin handler is active we may have to reload it
441 2 : bool force {pm_.checkLoadedPlugin(rootPath.string())};
442 :
443 : // We check if the preference is modified without having to reload plugin
444 2 : force &= preferencesm_.setPreference(key, value, rootPath.string(), acc);
445 2 : force &= callsm_.setPreference(key, value, rootPath.string());
446 2 : force &= chatsm_.setPreference(key, value, rootPath.string());
447 :
448 2 : if (force)
449 0 : unloadPlugin(rootPath.string());
450 :
451 : // Save preferences.msgpack with modified preferences values
452 2 : auto find = pluginPreferencesMap.find(key);
453 2 : if (find != pluginPreferencesMap.end()) {
454 2 : pluginUserPreferencesMap[key] = value;
455 2 : auto preferencesValuesFilePath = PluginPreferencesUtils::valuesFilePath(rootPath, acc);
456 2 : std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
457 2 : std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
458 2 : if (!fs.good()) {
459 0 : if (force) {
460 0 : loadPlugin(rootPath.string());
461 : }
462 0 : return false;
463 : }
464 : try {
465 2 : msgpack::pack(fs, pluginUserPreferencesMap);
466 0 : } catch (const std::exception& e) {
467 0 : JAMI_ERROR("{}", e.what());
468 0 : if (force) {
469 0 : loadPlugin(rootPath.string());
470 : }
471 0 : return false;
472 0 : }
473 2 : }
474 2 : if (force) {
475 0 : loadPlugin(rootPath.string());
476 : }
477 2 : return true;
478 2 : }
479 :
480 : std::map<std::string, std::string>
481 15 : JamiPluginManager::getPluginPreferencesValuesMap(const std::string& rootPath, const std::string& accountId)
482 : {
483 15 : return PluginPreferencesUtils::getPreferencesValuesMap(rootPath, accountId);
484 : }
485 :
486 : bool
487 3 : JamiPluginManager::resetPluginPreferencesValuesMap(const std::string& rootPath, const std::string& accountId)
488 : {
489 3 : bool acc {accountId.empty()};
490 3 : bool loaded {pm_.checkLoadedPlugin(rootPath)};
491 3 : if (loaded && acc)
492 0 : unloadPlugin(rootPath);
493 3 : auto status = PluginPreferencesUtils::resetPreferencesValuesMap(rootPath, accountId);
494 3 : preferencesm_.resetPreferences(rootPath, accountId);
495 3 : if (loaded && acc) {
496 0 : loadPlugin(rootPath);
497 : }
498 3 : return status;
499 : }
500 :
501 : void
502 33 : JamiPluginManager::registerServices()
503 : {
504 : // Register getPluginPreferences so that plugin's can receive it's preferences
505 99 : pm_.registerService("getPluginPreferences", [](const DLPlugin* plugin, void* data) {
506 0 : auto* ppp = static_cast<std::map<std::string, std::string>*>(data);
507 0 : *ppp = PluginPreferencesUtils::getPreferencesValuesMap(PluginUtils::getRootPathFromSoPath(plugin->getPath()));
508 0 : return SUCCESS;
509 : });
510 :
511 : // Register getPluginDataPath so that plugin's can receive the path to it's data folder
512 99 : pm_.registerService("getPluginDataPath", [](const DLPlugin* plugin, void* data) {
513 8 : auto* dataPath = static_cast<std::string*>(data);
514 8 : dataPath->assign(PluginUtils::dataPath(plugin->getPath()).string());
515 8 : return SUCCESS;
516 : });
517 :
518 : // getPluginAccPreferences is a service that allows plugins to load saved per account preferences.
519 0 : auto getPluginAccPreferences = [](const DLPlugin* plugin, void* data) {
520 0 : const auto path = PluginUtils::getRootPathFromSoPath(plugin->getPath());
521 0 : auto* preferencesPtr {(static_cast<PreferencesMap*>(data))};
522 0 : if (!preferencesPtr)
523 0 : return FAILURE;
524 :
525 0 : preferencesPtr->emplace("default", PluginPreferencesUtils::getPreferencesValuesMap(path, "default"));
526 :
527 0 : for (const auto& accId : jami::Manager::instance().getAccountList())
528 0 : preferencesPtr->emplace(accId, PluginPreferencesUtils::getPreferencesValuesMap(path, accId));
529 0 : return SUCCESS;
530 0 : };
531 :
532 99 : pm_.registerService("getPluginAccPreferences", getPluginAccPreferences);
533 :
534 : // getAccountUri returns the Jami URI (username) for a given account ID.
535 : // data must point to a std::pair<std::string, std::string> where
536 : // first = accountId (input) and second = uri (output).
537 99 : pm_.registerService("getAccountUri", [](const DLPlugin* /*plugin*/, void* data) {
538 0 : auto* p = static_cast<std::pair<std::string, std::string>*>(data);
539 0 : if (!p) {
540 0 : return FAILURE;
541 : }
542 0 : if (const auto acc = Manager::instance().getAccount<JamiAccount>(p->first)) {
543 0 : p->second = acc->getUsername();
544 0 : }
545 0 : return SUCCESS;
546 : });
547 :
548 : // getAccountIds returns all account IDs known to the daemon.
549 : // data must point to a std::vector<std::string>.
550 99 : pm_.registerService("getAccountIds", [](const DLPlugin* /*plugin*/, void* data) {
551 0 : auto* p = static_cast<std::vector<std::string>*>(data);
552 0 : if (!p) {
553 0 : return FAILURE;
554 : }
555 0 : *p = Manager::instance().getAccountList();
556 0 : return SUCCESS;
557 : });
558 :
559 : // setPluginAccPreference writes a single per-account preference to disk without
560 : // triggering handler callbacks.
561 : // data must point to std::tuple<std::string,std::string,std::string>: {accountId, key, value}.
562 99 : pm_.registerService("setPluginAccPreference", [](const DLPlugin* plugin, void* data) {
563 : using T = std::tuple<std::string, std::string, std::string>;
564 0 : auto* p = static_cast<T*>(data);
565 0 : if (!p) {
566 0 : return FAILURE;
567 : }
568 0 : const auto& [accountId, key, value] = *p;
569 0 : const auto rootPath = PluginUtils::getRootPathFromSoPath(plugin->getPath());
570 :
571 : // The key must exist in this account's effective preferences map.
572 0 : if (const auto prefsMap = PluginPreferencesUtils::getPreferencesValuesMap(rootPath, accountId);
573 0 : !prefsMap.contains(key)) {
574 0 : return FAILURE;
575 0 : }
576 :
577 0 : return PluginPreferencesUtils::setUserPreferenceValue(rootPath, accountId, key, value) ? SUCCESS : FAILURE;
578 0 : });
579 33 : }
580 :
581 : #ifdef LIBJAMI_TEST
582 : void
583 1 : JamiPluginManager::addPluginAuthority(const dht::crypto::Certificate& cert)
584 : {
585 1 : trust_.add(cert);
586 1 : }
587 : #endif
588 :
589 : } // namespace jami
|