Commit 42ad78f2 authored by Ivan Čukić's avatar Ivan Čukić 👁

Importing existing encrypted directories added to the backend

Adds the dbus command for importing vaults. Running

    qdbus org.kde.kded5 /modules/plasmavault requestImportVault

will start the vault importing wizard.

BUG:386200
parent b32b0e71
......@@ -31,6 +31,7 @@ set (
ui/vaultdeletionwidget.cpp
ui/vaultcreationwizard.cpp
ui/vaultimportingwizard.cpp
ui/vaultconfigurationwizard.cpp
ui/mountdialog.cpp
......@@ -51,6 +52,7 @@ ki18n_wrap_ui (
ui/vaultdeletionwidget.ui
ui/vaultcreationwizard.ui
ui/vaultimportingwizard.ui
ui/vaultconfigurationwizard.ui
ui/mountdialog.ui
)
......
......@@ -21,10 +21,10 @@
#ifndef PLASMAVAULT_KDED_ENGINE_BACKEND_P_H
#define PLASMAVAULT_KDED_ENGINE_BACKEND_P_H
#include <QDir>
#include <QList>
#include <QString>
#include <QStringList>
#include <QDir>
#include <memory>
......@@ -48,6 +48,11 @@ public:
const MountPoint &mountPoint,
const Vault::Payload &payload) = 0;
virtual FutureResult<> import(const QString &name,
const Device &device,
const MountPoint &mountPoint,
const Vault::Payload &payload) = 0;
virtual FutureResult<> open(const Device &device,
const MountPoint &mountPoint,
const Vault::Payload &payload) = 0;
......@@ -75,8 +80,10 @@ public:
{
QDir dir(path);
return !dir.exists() ? false
: !dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty();
if (!dir.exists()) return false;
return !dir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries)
.isEmpty();
}
};
......
......@@ -133,6 +133,28 @@ FutureResult<> FuseBackend::initialize(const QString &name,
FutureResult<> FuseBackend::import(const QString &name,
const Device &device,
const MountPoint &mountPoint,
const Vault::Payload &payload)
{
Q_UNUSED(name);
return
!isInitialized(device) ?
errorResult(Error::BackendError,
i18n("This directory doesn't contain encrypted data")) :
!directoryExists(device) || directoryExists(mountPoint) ?
errorResult(Error::BackendError,
i18n("You need to select an empty directory for the mount point")) :
// otherwise
mount(device, mountPoint, payload);
}
FutureResult<> FuseBackend::open(const Device &device,
const MountPoint &mountPoint,
const Vault::Payload &payload)
......
......@@ -38,6 +38,10 @@ public:
const Device &device, const MountPoint &mountPoint,
const Vault::Payload &payload) override;
FutureResult<> import(const QString &name,
const Device &device, const MountPoint &mountPoint,
const Vault::Payload &payload) override;
FutureResult<> open(const Device &device,
const MountPoint &mountPoint,
const Vault::Payload &payload) override;
......
......@@ -20,24 +20,32 @@
#include "types.h"
#include <QFileInfo>
namespace PlasmaVault {
static QString validateDevice(QString device)
static QString normalizePath(const QString& path)
{
if (device.endsWith('/')) {
device.chop(1);
QFileInfo fileInfo(path);
auto result = fileInfo.canonicalFilePath();
if (result.endsWith('/')) {
result.chop(1);
}
return device;
return result;
}
Device::Device(const QString &device)
: m_device(validateDevice(device))
: m_device(device)
{
}
Device::operator QString() const
{
return m_device;
// Done here because canonicalFilePath relies on file existence
return normalizePath(m_device);
}
MountPoint::MountPoint(const QString &mountPoint)
......@@ -47,7 +55,8 @@ MountPoint::MountPoint(const QString &mountPoint)
MountPoint::operator QString() const
{
return m_mountPoint;
// Done here because canonicalFilePath relies on file existence
return normalizePath(m_mountPoint);
}
} // namespace PlasmaVault
......
......@@ -204,8 +204,6 @@ public:
emit q->statusChanged(VaultInfo::Error);
}
config->sync();
}
......@@ -397,6 +395,42 @@ FutureResult<> Vault::create(const QString &name, const MountPoint &mountPoint,
}
FutureResult<> Vault::import(const QString &name, const MountPoint &mountPoint,
const Payload &payload)
{
using namespace AsynQt::operators;
return
// If the backend is already known, and the device is initialized,
// we do not want to do it again
d->data && (!d->data->backend->isInitialized(d->device)) ?
errorResult(Error::DeviceError,
i18n("This device is not initialized. Cannot import it.")) :
// Mount not open, check the error messages
!(d->data = d->loadVault(d->device, name, mountPoint, payload)) ?
errorResult(Error::BackendError,
i18n("Unknown error, unable to create the backend.")) :
// otherwise
d->followFuture(VaultInfo::Creating,
d->data->backend->import(name, d->device, mountPoint, payload))
| onSuccess([mountPoint] {
// If we have successfully created the vault,
// lets try to set its icon
QFile dotDir(mountPoint + "/.directory");
if (dotDir.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&dotDir);
out << "[Desktop Entry]\nIcon=folder-decrypted\n";
}
});
}
FutureResult<> Vault::open(const Payload &payload)
{
return
......
......@@ -52,8 +52,8 @@ class Vault: public QObject {
Q_PROPERTY(PlasmaVault::Device device READ device)
Q_PROPERTY(QString mountPoint READ mountPoint NOTIFY mountPointChanged)
Q_PROPERTY(VaultInfo::Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QString mountPoint READ mountPoint NOTIFY mountPointChanged)
Q_PROPERTY(VaultInfo::Status status READ status NOTIFY statusChanged)
Q_PROPERTY(bool isInitialized READ isInitialized NOTIFY isInitializedChanged)
Q_PROPERTY(bool isOpened READ isOpened NOTIFY isOpenedChanged)
......@@ -62,7 +62,7 @@ class Vault: public QObject {
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString message READ message NOTIFY messageChanged)
Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged)
Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged)
Q_PROPERTY(bool isOfflineOnly READ isOfflineOnly NOTIFY isOfflineOnlyChanged)
public:
......@@ -75,6 +75,8 @@ public:
FutureResult<> create(const QString &name, const MountPoint &mountPoint,
const Payload &payload);
FutureResult<> import(const QString &name, const MountPoint &mountPoint,
const Payload &payload);
FutureResult<> open(const Payload &payload);
FutureResult<> close();
......
......@@ -32,6 +32,7 @@
#include "engine/commandresult.h"
#include "ui/vaultcreationwizard.h"
#include "ui/vaultimportingwizard.h"
#include "ui/vaultconfigurationwizard.h"
#include "ui/mountdialog.h"
......@@ -124,13 +125,15 @@ PlasmaVaultService::PlasmaVaultService(QObject * parent, const QVariantList&)
: KDEDModule(parent)
, d(new Private())
{
connect(this, &KDEDModule::moduleRegistered, this,
&PlasmaVaultService::slotRegistered);
connect(this, &KDEDModule::moduleRegistered,
this, &PlasmaVaultService::slotRegistered);
connect(&d->kamd, &KActivities::Consumer::currentActivityChanged,
this, &PlasmaVaultService::onCurrentActivityChanged);
init();
for (const Device &device: Vault::availableDevices()) {
registerVault(new Vault(device, this));
}
}
......@@ -141,21 +144,11 @@ PlasmaVaultService::~PlasmaVaultService()
void PlasmaVaultService::init()
{
for (const Device &device: Vault::availableDevices()) {
registerVault(new Vault(device, this));
}
}
PlasmaVault::VaultInfoList PlasmaVaultService::availableDevices() const
{
PlasmaVault::VaultInfoList result;
for (const auto &vault: d->knownVaults.values()) {
const auto vaultData = vault->info();
result << vaultData;
result << vault->info();
}
return result;
}
......@@ -174,6 +167,18 @@ void PlasmaVaultService::requestNewVault()
void PlasmaVaultService::requestImportVault()
{
const auto dialog = new VaultImportingWizard();
connect(dialog, &VaultImportingWizard::importedVault,
this, &PlasmaVaultService::registerVault);
dialog->show();
}
void PlasmaVaultService::slotRegistered(const QDBusObjectPath &path)
{
if (path.path() == QLatin1String("/modules/plasmavault")) {
......@@ -234,7 +239,7 @@ void PlasmaVaultService::forgetVault(Vault* vault)
void PlasmaVaultService::onVaultStatusChanged(VaultInfo::Status status)
{
const auto vault = qobject_cast<Vault*>(sender());
const auto vault = static_cast<Vault*>(sender());
if (status == VaultInfo::Dismantled) {
forgetVault(vault);
......@@ -288,7 +293,7 @@ void PlasmaVaultService::onVaultStatusChanged(VaultInfo::Status status)
void PlasmaVaultService::onVaultInfoChanged()
{
const auto vault = qobject_cast<Vault*>(sender());
const auto vault = static_cast<Vault*>(sender());
emit vaultChanged(vault->info());
}
......@@ -297,9 +302,7 @@ void PlasmaVaultService::onVaultInfoChanged()
void PlasmaVaultService::onVaultMessageChanged(const QString &message)
{
Q_UNUSED(message);
const auto vault = qobject_cast<Vault*>(sender());
const auto vault = static_cast<Vault*>(sender());
emit vaultChanged(vault->info());
}
......@@ -311,6 +314,8 @@ void showPasswordMountDialog(Vault *vault, const std::function<void()> &function
}
//^
void PlasmaVaultService::openVault(const QString &device)
{
if (auto vault = d->vaultFor(device)) {
......@@ -341,9 +346,6 @@ void PlasmaVaultService::configureVault(const QString &device)
if (auto vault = d->vaultFor(device)) {
const auto dialog = new VaultConfigurationWizard(vault);
// connect(dialog, &VaultConfigurationWizard::configurationChanged,
// this, &PlasmaVaultService::registerVault);
dialog->show();
}
}
......
......@@ -37,9 +37,8 @@ public:
~PlasmaVaultService();
public Q_SLOTS:
Q_SCRIPTABLE void init();
Q_SCRIPTABLE void requestNewVault();
Q_SCRIPTABLE void requestImportVault();
Q_SCRIPTABLE void openVault(const QString &device);
Q_SCRIPTABLE void closeVault(const QString &device);
Q_SCRIPTABLE void forceCloseVault(const QString &device);
......
/*
* Copyright 2017 by Ivan Cukic <ivan.cukic (at) kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "vaultimportingwizard.h"
#include "ui_vaultimportingwizard.h"
#include <QPushButton>
#include <QMap>
#include <QVector>
#include <QStackedLayout>
#include "dialogdsl.h"
#include "vault.h"
using namespace DialogDsl;
using namespace DialogDsl::operators;
#include "backendchooserwidget.h"
#include "activitieslinkingwidget.h"
#include "cryfscypherchooserwidget.h"
#include "directorypairchooserwidget.h"
#include "noticewidget.h"
#include "passwordchooserwidget.h"
#include "offlineonlywidget.h"
class VaultImportingWizard::Private {
public:
VaultImportingWizard *const q;
Ui::VaultImportingWizard ui;
QPushButton *buttonPrevious;
QPushButton *buttonNext;
QPushButton *buttonImport;
QStackedLayout *layout;
inline void buttonNextSetEnabled(bool enabled) {
buttonNext->setEnabled(enabled);
buttonImport->setEnabled(enabled);
}
QVector<DialogDsl::DialogModule*> currentStepModules;
steps currentSteps;
BackendChooserWidget *firstStepModule = nullptr;
DialogDsl::DialogModule *currentModule = nullptr;
Logic logic
{
{ "encfs" / i18n("EncFS"),
{
step { directoryPairChooser(
DirectoryPairChooserWidget::ShowDevicePicker |
DirectoryPairChooserWidget::ShowMountPointPicker |
DirectoryPairChooserWidget::RequireExistingDevice |
DirectoryPairChooserWidget::RequireNewMountPoint
) },
step { passwordChooser() },
step {
activitiesChooser(),
offlineOnlyChooser()
}
}
},
{ "cryfs" / i18n("CryFS"),
{
step { directoryPairChooser(
DirectoryPairChooserWidget::ShowDevicePicker |
DirectoryPairChooserWidget::ShowMountPointPicker |
DirectoryPairChooserWidget::RequireExistingDevice |
DirectoryPairChooserWidget::RequireNewMountPoint
) },
step { passwordChooser() },
step {
activitiesChooser(),
offlineOnlyChooser()
}
}
}
};
// to suggest the highest priority to the user as a starting value
QMap<QString, int> priorities = {
{ "encfs", 1 },
{ "cryfs", 2 }
};
template <typename ClickHandler>
QPushButton *addDialogButton(const QString &icon, const QString &title, ClickHandler clickHandler)
{
auto button = new QPushButton(QIcon::fromTheme(icon), title);
ui.buttons->addButton(button, QDialogButtonBox::ActionRole);
QObject::connect(button, &QPushButton::clicked,
q, clickHandler);
return button;
}
Private(VaultImportingWizard *parent)
: q(parent)
{
ui.setupUi(parent);
ui.message->hide();
layout = new QStackedLayout();
layout->setContentsMargins(0, 0, 0, 0);
ui.container->setLayout(layout);
// The dialog buttons do not have previous/next by default
// so we need to create them
buttonPrevious = addDialogButton("go-previous", i18n("Previous"), [this] { previousStep(); });
buttonNext = addDialogButton("go-next", i18n("Next"), [this] { nextStep(); });
buttonImport = addDialogButton("dialog-ok-apply", i18n("Import"), [this] { importVault(); });
// The 'Import' button should be hidden by default
buttonImport->hide();
buttonPrevious->setEnabled(false);
buttonNextSetEnabled(false);
// Loading the fist page of the wizard
firstStepModule = new BackendChooserWidget();
setCurrentModule(firstStepModule);
layout->addWidget(firstStepModule);
// Loading the backends to the combo box
for (const auto& key: logic.keys()) {
firstStepModule->addItem(key, key.translation(), priorities.value(key));
}
firstStepModule->checkBackendAvailable();
}
void setCurrentModule(DialogDsl::DialogModule *module)
{
// If there is a current module already, disconnect it
if (currentModule) {
currentModule->aboutToBeHidden();
currentModule->disconnect();
}
// The current module needs to be changed
currentModule = module;
currentModule->aboutToBeShown();
QObject::connect(
currentModule, &DialogModule::isValidChanged,
q, [&] (bool valid) {
buttonNextSetEnabled(valid);
});
// Lets update the button states
// 1. next/Import button is enabled only if the current
// module is in the valid state
buttonNextSetEnabled(currentModule->isValid());
// 2. previous button is enabled only if we are not on
// the first page
buttonPrevious->setEnabled(currentStepModules.size() > 0);
// 3. If we have loaded the last page, we want to show the
// 'Import' button instead of 'Next'
if (!currentSteps.isEmpty() && currentStepModules.size() == currentSteps.size()) {
buttonNext->hide();
buttonImport->show();
} else {
buttonNext->show();
buttonImport->hide();
}
// Calling to initialize the module -- we are passing all the
// previously collected data to it
auto collectedPayload = firstStepModule->fields();
for (const auto* module: currentStepModules) {
collectedPayload.unite(module->fields());
}
currentModule->init(collectedPayload);
}
void previousStep()
{
if (currentStepModules.isEmpty()) return;
// We want to kill the current module, and move to the previous one
currentStepModules.takeLast();
currentModule->deleteLater();;
if (currentStepModules.size()) {
setCurrentModule(currentStepModules.last());
} else {
setCurrentModule(firstStepModule);
}
if (!currentModule->shouldBeShown()) {
previousStep();
}
}
void nextStep()
{
// If the step modules are empty, this means that we
// have just started - the user chose the backend
// and we need to load the vault creation steps
if (currentStepModules.isEmpty()) {
const auto &fields = firstStepModule->fields();
currentSteps = logic[fields[KEY_BACKEND].toByteArray()];
}
// Loading the modulws that we need to show now
auto subModules = currentSteps[currentStepModules.size()];
// If there is only one module on the current page,
// lets not complicate things by creating the compound module
DialogModule *stepWidget =
(subModules.size() == 1) ? subModules.first()()
: new CompoundDialogModule(subModules);
// Adding the widget to the list and the layout
currentStepModules << stepWidget;
layout->addWidget(stepWidget);
layout->setCurrentWidget(stepWidget);
// Set the newly added module to be the current
setCurrentModule(stepWidget);
if (!currentModule->shouldBeShown()) {
nextStep();
}
}
void importVault()
{
auto collectedPayload = firstStepModule->fields();
for (const auto* module: currentStepModules) {
collectedPayload.unite(module->fields());
}
const auto name = collectedPayload[KEY_NAME].toString();
const PlasmaVault::Device device(collectedPayload[KEY_DEVICE].toString());
const PlasmaVault::MountPoint mountPoint(collectedPayload[KEY_MOUNT_POINT].toString());
auto vault = new PlasmaVault::Vault(device, q);
auto future = vault->import(name, mountPoint, collectedPayload);
auto result = AsynQt::await(future);
if (result) {
emit q->importedVault(vault);
q->QDialog::accept();
} else {
ui.message->setText(result.error().message());
ui.message->setMessageType(KMessageWidget::Error);
ui.message->show();
vault->scheduleDeletion();
}
}
};
VaultImportingWizard::VaultImportingWizard(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
setWindowTitle(i18nc("@title:window", "Import an Existing Vault"));
}
VaultImportingWizard::~VaultImportingWizard()
{
}
/*
* Copyright 2017 by Ivan Cukic <ivan.cukic (at) kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PLASMAVAULT_KDED_UI_VAULT_IMPORTING_WIZARD_H
#define PLASMAVAULT_KDED_UI_VAULT_IMPORTING_WIZARD_H
#include <QDialog>
namespace PlasmaVault {
class Vault;
} // namespace PlasmaVault
class VaultImportingWizard: public QDialog {
Q_OBJECT
public:
VaultImportingWizard(QWidget *parent = nullptr);
~VaultImportingWizard();
Q_SIGNALS:
void importedVault(PlasmaVault::Vault *vault);
private:
class Private;
QScopedPointer<Private> d;
};
#endif // include guard
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VaultImportingWizard</class>
<widget class="QDialog" name="VaultImportingWizard">
<property name="geometry">