File indexing completed on 2024-04-21 16:20:45

0001 /*
0002  *   SPDX-FileCopyrightText: 2017, 2018, 2019 Ivan Cukic <ivan.cukic (at) kde.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "service.h"
0008 
0009 #include <QDBusObjectPath>
0010 #include <QMessageBox>
0011 #include <QProcess>
0012 
0013 #include <KActivities/Consumer>
0014 #include <KApplicationTrader>
0015 #include <KIO/ApplicationLauncherJob>
0016 #include <KLocalizedString>
0017 #include <KPasswordDialog>
0018 #include <KPluginFactory>
0019 #include <KService>
0020 
0021 #include "engine/commandresult.h"
0022 #include "engine/vault.h"
0023 
0024 #include "ui/mountdialog.h"
0025 #include "ui/vaultconfigurationdialog.h"
0026 #include "ui/vaultcreationwizard.h"
0027 #include "ui/vaultimportingwizard.h"
0028 
0029 #include <functional>
0030 
0031 #include <asynqt/operations/listen.h>
0032 
0033 #include <config-plasma-vault.h>
0034 #if HAVE_NETWORKMANAGER
0035 #include <NetworkManagerQt/Manager>
0036 #else
0037 namespace NetworkManager
0038 {
0039 bool isNetworkingEnabled()
0040 {
0041     return true;
0042 }
0043 
0044 void setNetworkingEnabled(bool enabled)
0045 {
0046     Q_UNUSED(enabled);
0047 }
0048 }
0049 #endif
0050 
0051 K_PLUGIN_FACTORY_WITH_JSON(PlasmaVaultServiceFactory, "plasmavault.json", registerPlugin<PlasmaVaultService>();)
0052 
0053 using namespace PlasmaVault;
0054 
0055 using AsynQt::Expected;
0056 
0057 class PlasmaVaultService::Private
0058 {
0059 public:
0060     QHash<Device, Vault *> knownVaults;
0061     QSet<Device> openVaults;
0062     KActivities::Consumer kamd;
0063 
0064     struct NetworkingState {
0065         bool wasNetworkingEnabled;
0066         QVector<QString> devicesInhibittingNetworking;
0067     };
0068     // Ideally, this would be std::optional... lovely C++17
0069     Expected<NetworkingState, int> savedNetworkingState = Expected<NetworkingState, int>::error(0);
0070 
0071     void saveNetworkingState()
0072     {
0073         // Ignore the request if we already have a saved state
0074         if (savedNetworkingState) {
0075             return;
0076         }
0077 
0078         savedNetworkingState = Expected<NetworkingState, int>::success(NetworkingState{NetworkManager::isNetworkingEnabled() || true, {}});
0079     }
0080 
0081     void restoreNetworkingState()
0082     {
0083         // Ignore the request if we do not have a state saved
0084         // or if there are more devices inhibitting networking
0085         if (!savedNetworkingState || !savedNetworkingState->devicesInhibittingNetworking.isEmpty()) {
0086             return;
0087         }
0088 
0089         NetworkManager::setNetworkingEnabled(savedNetworkingState->wasNetworkingEnabled);
0090     }
0091 
0092     Vault *vaultFor(const QString &device_) const
0093     {
0094         const Device device(device_);
0095 
0096         if (!knownVaults.contains(device)) {
0097             return nullptr;
0098         }
0099 
0100         return knownVaults[device];
0101     }
0102 };
0103 
0104 PlasmaVaultService::PlasmaVaultService(QObject *parent, const QVariantList &)
0105     : KDEDModule(parent)
0106     , d(new Private())
0107 {
0108     connect(this, &KDEDModule::moduleRegistered, this, &PlasmaVaultService::slotRegistered);
0109 
0110     // Close vaults that don't belong to the current activity
0111     connect(&d->kamd, &KActivities::Consumer::currentActivityChanged, this, &PlasmaVaultService::onCurrentActivityChanged);
0112 
0113     // When an activity is deleted, remove it from all the vaults
0114     connect(&d->kamd, &KActivities::Consumer::activityRemoved, this, &PlasmaVaultService::onActivityRemoved);
0115 
0116     // When activities are loaded, remove activities that no longer exist
0117     // the vaults
0118     connect(&d->kamd, &KActivities::Consumer::activitiesChanged, this, &PlasmaVaultService::onActivitiesChanged);
0119 
0120     for (const Device &device : Vault::availableDevices()) {
0121         registerVault(new Vault(device, this));
0122     }
0123 
0124     onActivitiesChanged(d->kamd.activities());
0125 }
0126 
0127 PlasmaVaultService::~PlasmaVaultService()
0128 {
0129 }
0130 
0131 PlasmaVault::VaultInfoList PlasmaVaultService::availableDevices() const
0132 {
0133     PlasmaVault::VaultInfoList result;
0134     for (const auto &vault : d->knownVaults.values()) {
0135         result << vault->info();
0136     }
0137     return result;
0138 }
0139 
0140 void PlasmaVaultService::requestNewVault()
0141 {
0142     const auto dialog = new VaultCreationWizard();
0143 
0144     connect(dialog, &VaultCreationWizard::createdVault, this, &PlasmaVaultService::registerVault);
0145 
0146     dialog->show();
0147 }
0148 
0149 void PlasmaVaultService::requestImportVault()
0150 {
0151     const auto dialog = new VaultImportingWizard();
0152 
0153     connect(dialog, &VaultImportingWizard::importedVault, this, &PlasmaVaultService::registerVault);
0154 
0155     dialog->show();
0156 }
0157 
0158 void PlasmaVaultService::slotRegistered(const QDBusObjectPath &path)
0159 {
0160     if (path.path() == QLatin1String("/modules/plasmavault")) {
0161         Q_EMIT registered();
0162     }
0163 }
0164 
0165 void PlasmaVaultService::registerVault(Vault *vault)
0166 {
0167     if (!vault->isValid()) {
0168         qWarning() << "Warning: Trying to register an invalid vault: " << vault->device().data();
0169         return;
0170     }
0171 
0172     if (d->knownVaults.contains(vault->device())) {
0173         qWarning() << "Warning: This one is already registered: " << vault->device().data();
0174         return;
0175     }
0176 
0177     vault->setParent(this);
0178 
0179     d->knownVaults[vault->device()] = vault;
0180 
0181     connect(vault, &Vault::statusChanged, this, &PlasmaVaultService::onVaultStatusChanged);
0182     connect(vault, &Vault::messageChanged, this, &PlasmaVaultService::onVaultMessageChanged);
0183     connect(vault, &Vault::infoChanged, this, &PlasmaVaultService::onVaultInfoChanged);
0184 
0185     Q_EMIT vaultAdded(vault->info());
0186 
0187     if (vault->status() == VaultInfo::Opened) {
0188         d->openVaults << vault->device();
0189     }
0190 }
0191 
0192 void PlasmaVaultService::forgetVault(Vault *vault)
0193 {
0194     // Can not be open
0195     // d->openVaults.remove(vault.device());
0196     // and therefore can not inhibit networking
0197     // ... d->savedNetworkingState ...
0198 
0199     Q_EMIT vaultRemoved(vault->device().data());
0200 
0201     d->knownVaults.remove(vault->device());
0202     vault->deleteLater();
0203 }
0204 
0205 void PlasmaVaultService::onVaultStatusChanged(VaultInfo::Status status)
0206 {
0207     const auto vault = static_cast<Vault *>(sender());
0208 
0209     if (status == VaultInfo::Dismantled) {
0210         forgetVault(vault);
0211 
0212     } else if (status == VaultInfo::Opened) {
0213         d->openVaults << vault->device();
0214         if (d->openVaults.size() == 1) {
0215             Q_EMIT hasOpenVaultsChanged(true);
0216         }
0217 
0218     } else {
0219         d->openVaults.remove(vault->device());
0220         if (d->openVaults.isEmpty()) {
0221             Q_EMIT hasOpenVaultsChanged(false);
0222         }
0223     }
0224 
0225     if (vault->isOfflineOnly()) {
0226         d->saveNetworkingState();
0227         auto &devicesInhibittingNetworking = d->savedNetworkingState->devicesInhibittingNetworking;
0228 
0229         // We need to check whether this vault
0230         // should be added or removed from the
0231         // inhibitors list
0232         const bool alreadyInhibiting = devicesInhibittingNetworking.contains(vault->device().data());
0233 
0234         if (status == VaultInfo::Opened && !alreadyInhibiting) {
0235             auto deviceOpeningHandle = "{opening}" + vault->device().data();
0236             devicesInhibittingNetworking.removeAll(deviceOpeningHandle);
0237             devicesInhibittingNetworking << vault->device().data();
0238         }
0239 
0240         if (status != VaultInfo::Opened && alreadyInhibiting) {
0241             devicesInhibittingNetworking.removeAll(vault->device().data());
0242         }
0243 
0244         // Now, let's handle the networking part
0245         if (!devicesInhibittingNetworking.isEmpty()) {
0246             NetworkManager::setNetworkingEnabled(false);
0247         }
0248 
0249         d->restoreNetworkingState();
0250     }
0251 
0252     Q_EMIT vaultChanged(vault->info());
0253 }
0254 
0255 void PlasmaVaultService::onVaultInfoChanged()
0256 {
0257     const auto vault = static_cast<Vault *>(sender());
0258     Q_EMIT vaultChanged(vault->info());
0259 }
0260 
0261 void PlasmaVaultService::onVaultMessageChanged(const QString &message)
0262 {
0263     Q_UNUSED(message);
0264     const auto vault = static_cast<Vault *>(sender());
0265     Q_EMIT vaultChanged(vault->info());
0266 }
0267 
0268 template<typename OnAccepted, typename OnRejected>
0269 void showPasswordMountDialog(Vault *vault, OnAccepted onAccepted, OnRejected onRejected)
0270 {
0271     auto dialog = new MountDialog(vault);
0272 
0273     QObject::connect(dialog, &QDialog::accepted, vault, onAccepted);
0274     QObject::connect(dialog, &QDialog::rejected, vault, onRejected);
0275 
0276     dialog->open();
0277 }
0278 //^
0279 
0280 void PlasmaVaultService::openVault(const QString &device)
0281 {
0282     if (auto vault = d->vaultFor(device)) {
0283         if (vault->isOpened())
0284             return;
0285 
0286         if (vault->isOfflineOnly()) {
0287             d->saveNetworkingState();
0288 
0289             auto &devicesInhibittingNetworking = d->savedNetworkingState->devicesInhibittingNetworking;
0290             auto deviceOpeningHandle = "{opening}" + vault->device().data();
0291 
0292             // We need to check whether this vault
0293             // should be added or removed from the
0294             // inhibitors list
0295             const bool alreadyInhibiting = devicesInhibittingNetworking.contains(deviceOpeningHandle);
0296 
0297             if (!alreadyInhibiting) {
0298                 devicesInhibittingNetworking << deviceOpeningHandle;
0299             }
0300 
0301             NetworkManager::setNetworkingEnabled(false);
0302         }
0303 
0304         auto stopInhibiting = [this, vault] {
0305             if (d->savedNetworkingState) {
0306                 auto &devicesInhibittingNetworking = d->savedNetworkingState->devicesInhibittingNetworking;
0307                 auto deviceOpeningHandle = "{opening}" + vault->device().data();
0308                 devicesInhibittingNetworking.removeAll(deviceOpeningHandle);
0309             }
0310         };
0311 
0312         showPasswordMountDialog(
0313             vault,
0314             [this, vault, stopInhibiting] {
0315                 Q_EMIT vaultChanged(vault->info());
0316                 stopInhibiting();
0317             },
0318             [this, vault, stopInhibiting] {
0319                 stopInhibiting();
0320                 if (vault->status() != VaultInfo::Opened) {
0321                     d->restoreNetworkingState();
0322                 }
0323             });
0324     }
0325 }
0326 
0327 void PlasmaVaultService::closeVault(const QString &device)
0328 {
0329     if (auto vault = d->vaultFor(device)) {
0330         if (!vault->isOpened())
0331             return;
0332 
0333         vault->close();
0334     }
0335 }
0336 
0337 void PlasmaVaultService::configureVault(const QString &device)
0338 {
0339     if (auto vault = d->vaultFor(device)) {
0340         const auto dialog = new VaultConfigurationDialog(vault);
0341 
0342         dialog->show();
0343     }
0344 }
0345 
0346 void PlasmaVaultService::forceCloseVault(const QString &device)
0347 {
0348     if (auto vault = d->vaultFor(device)) {
0349         if (!vault->isOpened())
0350             return;
0351 
0352         vault->forceClose();
0353     }
0354 }
0355 
0356 void PlasmaVaultService::openVaultInFileManager(const QString &device)
0357 {
0358     auto openFileManager = [this](const auto &vault) {
0359         KService::Ptr service = KApplicationTrader::preferredService(QStringLiteral("inode/directory"));
0360 
0361         // A hack to always open a new dolphin window
0362         // BUG: 445542
0363         // https://bugs.kde.org/show_bug.cgi?id=445542
0364         if (service->desktopEntryName() == QStringLiteral("org.kde.dolphin")) {
0365             service->setExec(service->exec() + QStringLiteral(" --new-window"));
0366         }
0367 
0368         auto *job = new KIO::ApplicationLauncherJob(service, this);
0369         job->setUrls({QUrl::fromLocalFile((QString)vault->mountPoint().data())});
0370         job->start();
0371     };
0372 
0373     if (auto vault = d->vaultFor(device)) {
0374         if (vault->isOpened()) {
0375             openFileManager(vault);
0376 
0377         } else {
0378             showPasswordMountDialog(
0379                 vault,
0380                 [this, vault, openFileManager] {
0381                     Q_EMIT vaultChanged(vault->info());
0382                     openFileManager(vault);
0383                 },
0384                 [this, vault] {
0385                     if (vault->status() != VaultInfo::Opened && d->savedNetworkingState) {
0386                         auto &devicesInhibittingNetworking = d->savedNetworkingState->devicesInhibittingNetworking;
0387                         devicesInhibittingNetworking.removeAll(vault->device().data());
0388                         d->restoreNetworkingState();
0389                     }
0390                 });
0391         }
0392     }
0393 }
0394 
0395 bool PlasmaVaultService::hasOpenVaults() const
0396 {
0397     return !d->openVaults.isEmpty();
0398 }
0399 
0400 void PlasmaVaultService::closeAllVaults()
0401 {
0402     for (const auto &device : d->openVaults) {
0403         closeVault(device.data());
0404     }
0405 }
0406 
0407 void PlasmaVaultService::forceCloseAllVaults()
0408 {
0409     for (const auto &device : d->openVaults) {
0410         forceCloseVault(device.data());
0411     }
0412 }
0413 
0414 void PlasmaVaultService::deleteVault(const QString &device, const QString &name)
0415 {
0416     if (!d->knownVaults.contains(Device(device))) {
0417         qWarning() << "The specified vault does not exist: " << device;
0418         return;
0419     }
0420 
0421     auto vault = d->knownVaults[Device(device)];
0422 
0423     if (vault->status() == VaultInfo::Opened) {
0424         qWarning() << "Can not delete an open vault: " << device;
0425         return;
0426     }
0427 
0428     if (vault->name() != name) {
0429         qWarning() << "Name is not correct: " << device;
0430         return;
0431     }
0432 
0433     AsynQt::onFinished(vault->dismantle({}), [](const auto &future) {
0434         const auto &result = future.result();
0435         if (result)
0436             return;
0437 
0438         const auto &error = result.error();
0439         if (error.code() != Error::OperationCancelled) {
0440             QMessageBox::critical(nullptr, i18n("Error deleting vault"), error.message());
0441         }
0442     });
0443 }
0444 
0445 void PlasmaVaultService::updateStatus()
0446 {
0447     for (const auto &device : d->knownVaults.keys()) {
0448         auto vault = d->knownVaults[device];
0449         vault->updateStatus();
0450     }
0451 }
0452 
0453 void PlasmaVaultService::onActivitiesChanged(const QStringList &knownActivities)
0454 {
0455     if (knownActivities == QStringList{"00000000-0000-0000-0000-000000000000"})
0456         return;
0457     qDebug() << "Known activities:" << knownActivities;
0458 
0459     for (auto *vault : d->knownVaults.values()) {
0460         auto vaultActivities = vault->activities();
0461         const auto removedBegin = std::remove_if(vaultActivities.begin(), vaultActivities.end(), [&knownActivities](const QString &vaultActivity) {
0462             return !knownActivities.contains(vaultActivity);
0463         });
0464         if (removedBegin != vaultActivities.end()) {
0465             vaultActivities.erase(removedBegin, vaultActivities.end());
0466             vault->setActivities(vaultActivities);
0467             vault->saveConfiguration();
0468         }
0469     }
0470 }
0471 
0472 void PlasmaVaultService::onCurrentActivityChanged(const QString &currentActivity)
0473 {
0474     for (auto *vault : d->knownVaults.values()) {
0475         const auto vaultActivities = vault->activities();
0476         if (!vaultActivities.isEmpty() && !vaultActivities.contains(currentActivity)) {
0477             vault->close();
0478         }
0479     }
0480 }
0481 
0482 void PlasmaVaultService::onActivityRemoved(const QString &removedActivity)
0483 {
0484     for (auto *vault : d->knownVaults.values()) {
0485         auto vaultActivities = vault->activities();
0486         if (vaultActivities.removeAll(removedActivity) > 0) {
0487             vault->setActivities(vaultActivities);
0488             vault->saveConfiguration();
0489             Q_EMIT vaultChanged(vault->info());
0490         }
0491     }
0492 }
0493 
0494 #include "service.moc"