mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
FdoSecrets: Major Refactor and Code Consolidation (#5747)
* Fixes #3837 * Change objects to use DBusMgr rather than separate adaptors - Update all DBus invokable methods to new parameter order - Change all usage of DBusReturn to simpler DBusResult - Use DBusMgr to handle path and service registration - Remove adaptor/* - Set path in DBusObject - Unregister service when service is destroyed - Restore handling of invalid QVariant in prompt complete signal - Clean up meta type registration - Move dbus related file together - Convert to QSharedPointer as much as possible - Fix mapping of the Delete method - Handle dbus property get all * Add per-client states - Move cipher negotiation to DBusClient - Show list of clients instead of sessions in the settings page - Add settings for confirmation of accessing items - Fix infinite recursion when client disconnected - Use optional explicit DBusClient parameter instead. This makes accessing the client info in an async context explicit, and thus prevent accidental assertions in prompts. * Improve User Interface - Add per-item access confirmation (if enabled) - Remove the "disable for site" button for the access control dialog - Improve the text on the settings page to be more consistent - Fix disconnect buttons in settings page not working - Make the unlock prompt method nonblocking * Fix and cleanup unit tests - Use QTRY_COMPARE when checking signal spies, as dbus signals are threaded - Fixes in meta type registration and type conversion - Remove QStringLiteral in COMPARE macros, making diff output readable - Add testing for remembering auth decision
This commit is contained in:
parent
33e6da33ca
commit
9a8a5a0006
@ -173,7 +173,8 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
// FdoSecrets
|
||||
{Config::FdoSecrets_Enabled, {QS("FdoSecrets/Enabled"), Roaming, false}},
|
||||
{Config::FdoSecrets_ShowNotification, {QS("FdoSecrets/ShowNotification"), Roaming, true}},
|
||||
{Config::FdoSecrets_NoConfirmDeleteItem, {QS("FdoSecrets/NoConfirmDeleteItem"), Roaming, false}},
|
||||
{Config::FdoSecrets_ConfirmDeleteItem, {QS("FdoSecrets/ConfirmDeleteItem"), Roaming, true}},
|
||||
{Config::FdoSecrets_ConfirmAccessItem, {QS("FdoSecrets/ConfirmAccessItem"), Roaming, true}},
|
||||
|
||||
// KeeShare
|
||||
{Config::KeeShare_QuietSuccess, {QS("KeeShare/QuietSuccess"), Roaming, false}},
|
||||
|
@ -151,7 +151,8 @@ public:
|
||||
|
||||
FdoSecrets_Enabled,
|
||||
FdoSecrets_ShowNotification,
|
||||
FdoSecrets_NoConfirmDeleteItem,
|
||||
FdoSecrets_ConfirmDeleteItem,
|
||||
FdoSecrets_ConfirmAccessItem,
|
||||
|
||||
KeeShare_QuietSuccess,
|
||||
KeeShare_Own,
|
||||
|
@ -53,6 +53,15 @@ enum IconSize
|
||||
Large
|
||||
};
|
||||
|
||||
enum class AuthDecision
|
||||
{
|
||||
Undecided,
|
||||
Allowed,
|
||||
AllowedOnce,
|
||||
Denied,
|
||||
DeniedOnce,
|
||||
};
|
||||
|
||||
template <typename T> struct AddConst
|
||||
{
|
||||
typedef const T Type;
|
||||
|
@ -6,11 +6,15 @@ if(WITH_XC_FDOSECRETS)
|
||||
FdoSecretsPlugin.cpp
|
||||
widgets/SettingsModels.cpp
|
||||
widgets/SettingsWidgetFdoSecrets.cpp
|
||||
widgets/RowButtonHelper.cpp
|
||||
|
||||
# per database settings page
|
||||
DatabaseSettingsPageFdoSecrets.cpp
|
||||
widgets/DatabaseSettingsWidgetFdoSecrets.cpp
|
||||
|
||||
# prompt dialog
|
||||
widgets/AccessControlDialog.cpp
|
||||
|
||||
# setting storage
|
||||
FdoSecretsSettings.cpp
|
||||
|
||||
@ -18,20 +22,17 @@ if(WITH_XC_FDOSECRETS)
|
||||
GcryptMPI.cpp
|
||||
|
||||
# dbus objects
|
||||
objects/DBusObject.cpp
|
||||
dbus/DBusClient.cpp
|
||||
dbus/DBusMgr.cpp
|
||||
dbus/DBusDispatch.cpp
|
||||
dbus/DBusObject.cpp
|
||||
objects/Service.cpp
|
||||
objects/Session.cpp
|
||||
objects/SessionCipher.cpp
|
||||
objects/Collection.cpp
|
||||
objects/Item.cpp
|
||||
objects/Prompt.cpp
|
||||
objects/adaptors/ServiceAdaptor.cpp
|
||||
objects/adaptors/SessionAdaptor.cpp
|
||||
objects/adaptors/CollectionAdaptor.cpp
|
||||
objects/adaptors/ItemAdaptor.cpp
|
||||
objects/adaptors/PromptAdaptor.cpp
|
||||
objects/DBusReturn.cpp
|
||||
objects/DBusTypes.cpp
|
||||
dbus/DBusTypes.cpp
|
||||
)
|
||||
target_link_libraries(fdosecrets Qt5::Core Qt5::Widgets Qt5::DBus ${GCRYPT_LIBRARIES})
|
||||
endif()
|
||||
|
@ -18,14 +18,14 @@
|
||||
#include "FdoSecretsPlugin.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/objects/DBusTypes.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/dbus/DBusTypes.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/widgets/SettingsWidgetFdoSecrets.h"
|
||||
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
using FdoSecrets::DBusMgr;
|
||||
using FdoSecrets::Service;
|
||||
|
||||
// TODO: Only used for testing. Need to split service functions away from settings page.
|
||||
@ -33,9 +33,13 @@ QPointer<FdoSecretsPlugin> g_fdoSecretsPlugin;
|
||||
|
||||
FdoSecretsPlugin::FdoSecretsPlugin(DatabaseTabWidget* tabWidget)
|
||||
: m_dbTabs(tabWidget)
|
||||
, m_dbus(new DBusMgr())
|
||||
{
|
||||
registerDBusTypes(m_dbus);
|
||||
m_dbus->populateMethodCache();
|
||||
|
||||
connect(m_dbus.data(), &DBusMgr::error, this, &FdoSecretsPlugin::emitError);
|
||||
g_fdoSecretsPlugin = this;
|
||||
FdoSecrets::registerDBusTypes();
|
||||
}
|
||||
|
||||
FdoSecretsPlugin* FdoSecretsPlugin::getPlugin()
|
||||
@ -63,7 +67,7 @@ void FdoSecretsPlugin::updateServiceState()
|
||||
{
|
||||
if (FdoSecrets::settings()->isEnabled()) {
|
||||
if (!m_secretService && m_dbTabs) {
|
||||
m_secretService = Service::Create(this, m_dbTabs);
|
||||
m_secretService = Service::Create(this, m_dbTabs, m_dbus);
|
||||
if (!m_secretService) {
|
||||
FdoSecrets::settings()->setEnabled(false);
|
||||
return;
|
||||
@ -88,6 +92,11 @@ DatabaseTabWidget* FdoSecretsPlugin::dbTabs() const
|
||||
return m_dbTabs;
|
||||
}
|
||||
|
||||
const QSharedPointer<FdoSecrets::DBusMgr>& FdoSecretsPlugin::dbus() const
|
||||
{
|
||||
return m_dbus;
|
||||
}
|
||||
|
||||
void FdoSecretsPlugin::emitRequestSwitchToDatabases()
|
||||
{
|
||||
emit requestSwitchToDatabases();
|
||||
@ -106,29 +115,3 @@ void FdoSecretsPlugin::emitError(const QString& msg)
|
||||
emit error(tr("<b>Fdo Secret Service:</b> %1").arg(msg));
|
||||
qDebug() << msg;
|
||||
}
|
||||
|
||||
QString FdoSecretsPlugin::reportExistingService() const
|
||||
{
|
||||
auto pidStr = tr("Unknown", "Unknown PID");
|
||||
auto exeStr = tr("Unknown", "Unknown executable path");
|
||||
|
||||
// try get pid
|
||||
auto pid = QDBusConnection::sessionBus().interface()->servicePid(DBUS_SERVICE_SECRET);
|
||||
if (pid.isValid()) {
|
||||
pidStr = QString::number(pid.value());
|
||||
|
||||
// try get the first part of the cmdline, which usually is the executable name/path
|
||||
QFile proc(QStringLiteral("/proc/%1/cmdline").arg(pid.value()));
|
||||
if (proc.open(QFile::ReadOnly)) {
|
||||
auto parts = proc.readAll().split('\0');
|
||||
if (parts.length() >= 1) {
|
||||
exeStr = QString::fromLocal8Bit(parts[0]).trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
auto otherService = tr("<i>PID: %1, Executable: %2</i>", "<i>PID: 1234, Executable: /path/to/exe</i>")
|
||||
.arg(pidStr, exeStr.toHtmlEscaped());
|
||||
return tr("Another secret service is running (%1).<br/>"
|
||||
"Please stop/remove it before re-enabling the Secret Service Integration.")
|
||||
.arg(otherService);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class DatabaseTabWidget;
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Service;
|
||||
class DBusMgr;
|
||||
} // namespace FdoSecrets
|
||||
|
||||
class FdoSecretsPlugin : public QObject, public ISettingsPage
|
||||
@ -66,10 +67,10 @@ public:
|
||||
DatabaseTabWidget* dbTabs() const;
|
||||
|
||||
/**
|
||||
* Check the running secret service and returns info about it
|
||||
* @return html string suitable to be shown in the UI
|
||||
* @brief The dbus manager instance
|
||||
* @return
|
||||
*/
|
||||
QString reportExistingService() const;
|
||||
const QSharedPointer<FdoSecrets::DBusMgr>& dbus() const;
|
||||
|
||||
// TODO: Only used for testing. Need to split service functions away from settings page.
|
||||
static FdoSecretsPlugin* getPlugin();
|
||||
@ -93,6 +94,7 @@ signals:
|
||||
|
||||
private:
|
||||
QPointer<DatabaseTabWidget> m_dbTabs;
|
||||
QSharedPointer<FdoSecrets::DBusMgr> m_dbus;
|
||||
QSharedPointer<FdoSecrets::Service> m_secretService;
|
||||
};
|
||||
|
||||
|
@ -64,14 +64,24 @@ namespace FdoSecrets
|
||||
config()->set(Config::FdoSecrets_ShowNotification, show);
|
||||
}
|
||||
|
||||
bool FdoSecretsSettings::noConfirmDeleteItem() const
|
||||
bool FdoSecretsSettings::confirmDeleteItem() const
|
||||
{
|
||||
return config()->get(Config::FdoSecrets_NoConfirmDeleteItem).toBool();
|
||||
return config()->get(Config::FdoSecrets_ConfirmDeleteItem).toBool();
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setNoConfirmDeleteItem(bool noConfirm)
|
||||
void FdoSecretsSettings::setConfirmDeleteItem(bool confirm)
|
||||
{
|
||||
config()->set(Config::FdoSecrets_NoConfirmDeleteItem, noConfirm);
|
||||
config()->set(Config::FdoSecrets_ConfirmDeleteItem, confirm);
|
||||
}
|
||||
|
||||
bool FdoSecretsSettings::confirmAccessItem() const
|
||||
{
|
||||
return config()->get(Config::FdoSecrets_ConfirmAccessItem).toBool();
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setConfirmAccessItem(bool confirmAccessItem)
|
||||
{
|
||||
config()->set(Config::FdoSecrets_ConfirmAccessItem, confirmAccessItem);
|
||||
}
|
||||
|
||||
QUuid FdoSecretsSettings::exposedGroup(const QSharedPointer<Database>& db) const
|
||||
|
@ -38,8 +38,11 @@ namespace FdoSecrets
|
||||
bool showNotification() const;
|
||||
void setShowNotification(bool show);
|
||||
|
||||
bool noConfirmDeleteItem() const;
|
||||
void setNoConfirmDeleteItem(bool noConfirm);
|
||||
bool confirmDeleteItem() const;
|
||||
void setConfirmDeleteItem(bool confirm);
|
||||
|
||||
bool confirmAccessItem() const;
|
||||
void setConfirmAccessItem(bool confirmAccessItem);
|
||||
|
||||
// Per db settings
|
||||
|
||||
|
144
src/fdosecrets/dbus/DBusClient.cpp
Normal file
144
src/fdosecrets/dbus/DBusClient.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
|
||||
* Copyright (C) 2020 Jan Klötzke <jan@kloetzke.net>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "DBusClient.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
DBusClient::DBusClient(DBusMgr* dbus, const QString& address, uint pid, const QString& name)
|
||||
: m_dbus(dbus)
|
||||
, m_address(address)
|
||||
, m_pid(pid)
|
||||
, m_name(name)
|
||||
{
|
||||
}
|
||||
|
||||
bool DBusClient::itemKnown(const QUuid& uuid) const
|
||||
{
|
||||
return m_authorizedAll || m_allowed.contains(uuid) || m_allowedOnce.contains(uuid) || m_denied.contains(uuid)
|
||||
|| m_deniedOnce.contains(uuid);
|
||||
}
|
||||
|
||||
bool DBusClient::itemAuthorized(const QUuid& uuid) const
|
||||
{
|
||||
if (!FdoSecrets::settings()->confirmAccessItem()) {
|
||||
// everyone is authorized if this is not enabled
|
||||
return true;
|
||||
}
|
||||
if (m_authorizedAll) {
|
||||
// this client is trusted
|
||||
return true;
|
||||
}
|
||||
if (m_deniedOnce.contains(uuid) || m_denied.contains(uuid)) {
|
||||
// explicitly denied
|
||||
return false;
|
||||
}
|
||||
if (m_allowedOnce.contains(uuid) || m_allowed.contains(uuid)) {
|
||||
// explicitly allowed
|
||||
return true;
|
||||
}
|
||||
// haven't asked, not authorized by default
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBusClient::itemAuthorizedResetOnce(const QUuid& uuid)
|
||||
{
|
||||
auto auth = itemAuthorized(uuid);
|
||||
m_deniedOnce.remove(uuid);
|
||||
m_allowedOnce.remove(uuid);
|
||||
return auth;
|
||||
}
|
||||
|
||||
void DBusClient::setItemAuthorized(const QUuid& uuid, AuthDecision auth)
|
||||
{
|
||||
// uuid should only be in exactly one set at any time
|
||||
m_allowed.remove(uuid);
|
||||
m_allowedOnce.remove(uuid);
|
||||
m_denied.remove(uuid);
|
||||
m_deniedOnce.remove(uuid);
|
||||
switch (auth) {
|
||||
case AuthDecision::Allowed:
|
||||
m_allowed.insert(uuid);
|
||||
break;
|
||||
case AuthDecision::AllowedOnce:
|
||||
m_allowedOnce.insert(uuid);
|
||||
break;
|
||||
case AuthDecision::Denied:
|
||||
m_denied.insert(uuid);
|
||||
break;
|
||||
case AuthDecision::DeniedOnce:
|
||||
m_deniedOnce.insert(uuid);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DBusClient::setAllAuthorized(bool authorized)
|
||||
{
|
||||
m_authorizedAll = authorized;
|
||||
}
|
||||
|
||||
void DBusClient::clearAuthorization()
|
||||
{
|
||||
m_authorizedAll = false;
|
||||
m_allowed.clear();
|
||||
m_allowedOnce.clear();
|
||||
m_denied.clear();
|
||||
m_deniedOnce.clear();
|
||||
}
|
||||
|
||||
void DBusClient::disconnectDBus()
|
||||
{
|
||||
clearAuthorization();
|
||||
// notify DBusMgr about the removal
|
||||
m_dbus->removeClient(this);
|
||||
}
|
||||
|
||||
QSharedPointer<CipherPair>
|
||||
DBusClient::negotiateCipher(const QString& algorithm, const QVariant& input, QVariant& output, bool& incomplete)
|
||||
{
|
||||
incomplete = false;
|
||||
|
||||
QSharedPointer<CipherPair> cipher{};
|
||||
if (algorithm == PlainCipher::Algorithm) {
|
||||
cipher.reset(new PlainCipher);
|
||||
} else if (algorithm == DhIetf1024Sha256Aes128CbcPkcs7::Algorithm) {
|
||||
QByteArray clientPublicKey = input.toByteArray();
|
||||
cipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7(clientPublicKey));
|
||||
} else {
|
||||
// error notSupported
|
||||
}
|
||||
|
||||
if (!cipher) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!cipher->isValid()) {
|
||||
qWarning() << "FdoSecrets: Error creating cipher";
|
||||
return {};
|
||||
}
|
||||
|
||||
output = cipher->negotiationOutput();
|
||||
return cipher;
|
||||
}
|
||||
} // namespace FdoSecrets
|
146
src/fdosecrets/dbus/DBusClient.h
Normal file
146
src/fdosecrets/dbus/DBusClient.h
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
|
||||
* Copyright (C) 2020 Jan Klötzke <jan@kloetzke.net>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSCLIENT_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSCLIENT_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class DBusMgr;
|
||||
class CipherPair;
|
||||
|
||||
/**
|
||||
* Represent a client that has made requests to our service. A client is identified by its
|
||||
* DBus address, which is guaranteed to be unique by the DBus protocol.
|
||||
*
|
||||
* An object of this class is created on the first request and destroyed
|
||||
* when the client address vanishes from the bus. DBus guarantees that the
|
||||
* client address is not reused.
|
||||
*
|
||||
* One client may have multiple `Session`s with our service, and this class
|
||||
* manages the negotiation state (if any) of ciphers and per-client authorization
|
||||
* status.
|
||||
*/
|
||||
class DBusClient
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Given peer's service address, construct a client object
|
||||
* @param address obtained from `QDBusMessage::service()`
|
||||
* @param pid the process PID
|
||||
* @param name the process name
|
||||
*/
|
||||
explicit DBusClient(DBusMgr* dbus, const QString& address, uint pid, const QString& name);
|
||||
|
||||
DBusMgr* dbus() const
|
||||
{
|
||||
return m_dbus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The human readable client name, usually the process name
|
||||
*/
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The unique DBus address of the client
|
||||
*/
|
||||
QString address() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The process id of the client
|
||||
*/
|
||||
uint pid() const
|
||||
{
|
||||
return m_pid;
|
||||
}
|
||||
|
||||
QSharedPointer<CipherPair>
|
||||
negotiateCipher(const QString& algorithm, const QVariant& input, QVariant& output, bool& incomplete);
|
||||
|
||||
/**
|
||||
* Check if the item is known in this client's auth list
|
||||
*/
|
||||
bool itemKnown(const QUuid& uuid) const;
|
||||
|
||||
/**
|
||||
* Check if client may access item identified by @a uuid.
|
||||
*/
|
||||
bool itemAuthorized(const QUuid& uuid) const;
|
||||
|
||||
/**
|
||||
* Check if client may access item identified by @a uuid, and also reset any once auth.
|
||||
*/
|
||||
bool itemAuthorizedResetOnce(const QUuid& uuid);
|
||||
|
||||
/**
|
||||
* Authorize client to access item identified by @a uuid.
|
||||
*/
|
||||
void setItemAuthorized(const QUuid& uuid, AuthDecision auth);
|
||||
|
||||
/**
|
||||
* Authorize client to access all items.
|
||||
*/
|
||||
void setAllAuthorized(bool authorized = true);
|
||||
|
||||
/**
|
||||
* Forget all previous authorization.
|
||||
*/
|
||||
void clearAuthorization();
|
||||
|
||||
/**
|
||||
* Forcefully disconnect the client.
|
||||
* Force close any remaining session, and cleanup negotiation states
|
||||
*/
|
||||
void disconnectDBus();
|
||||
|
||||
private:
|
||||
QPointer<DBusMgr> m_dbus;
|
||||
QString m_address;
|
||||
|
||||
uint m_pid{0};
|
||||
QString m_name{};
|
||||
|
||||
bool m_authorizedAll{false};
|
||||
|
||||
QSet<QUuid> m_allowed{};
|
||||
QSet<QUuid> m_denied{};
|
||||
|
||||
QSet<QUuid> m_allowedOnce{};
|
||||
QSet<QUuid> m_deniedOnce{};
|
||||
};
|
||||
|
||||
using DBusClientPtr = QSharedPointer<DBusClient>;
|
||||
} // namespace FdoSecrets
|
||||
Q_DECLARE_METATYPE(FdoSecrets::DBusClientPtr);
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSCLIENT_H
|
49
src/fdosecrets/dbus/DBusConstants.h
Normal file
49
src/fdosecrets/dbus/DBusConstants.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSCONSTANTS_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSCONSTANTS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
static const auto DBUS_SERVICE_SECRET = QStringLiteral("org.freedesktop.secrets");
|
||||
|
||||
#define DBUS_INTERFACE_SECRET_SERVICE_LITERAL "org.freedesktop.Secret.Service"
|
||||
#define DBUS_INTERFACE_SECRET_SESSION_LITERAL "org.freedesktop.Secret.Session"
|
||||
#define DBUS_INTERFACE_SECRET_COLLECTION_LITERAL "org.freedesktop.Secret.Collection"
|
||||
#define DBUS_INTERFACE_SECRET_ITEM_LITERAL "org.freedesktop.Secret.Item"
|
||||
#define DBUS_INTERFACE_SECRET_PROMPT_LITERAL "org.freedesktop.Secret.Prompt"
|
||||
|
||||
static const auto DBUS_INTERFACE_SECRET_SERVICE = QStringLiteral(DBUS_INTERFACE_SECRET_SERVICE_LITERAL);
|
||||
static const auto DBUS_INTERFACE_SECRET_SESSION = QStringLiteral(DBUS_INTERFACE_SECRET_SESSION_LITERAL);
|
||||
static const auto DBUS_INTERFACE_SECRET_COLLECTION = QStringLiteral(DBUS_INTERFACE_SECRET_COLLECTION_LITERAL);
|
||||
static const auto DBUS_INTERFACE_SECRET_ITEM = QStringLiteral(DBUS_INTERFACE_SECRET_ITEM_LITERAL);
|
||||
static const auto DBUS_INTERFACE_SECRET_PROMPT = QStringLiteral(DBUS_INTERFACE_SECRET_PROMPT_LITERAL);
|
||||
|
||||
static const auto DBUS_ERROR_SECRET_NO_SESSION = QStringLiteral("org.freedesktop.Secret.Error.NoSession");
|
||||
static const auto DBUS_ERROR_SECRET_NO_SUCH_OBJECT = QStringLiteral("org.freedesktop.Secret.Error.NoSuchObject");
|
||||
static const auto DBUS_ERROR_SECRET_IS_LOCKED = QStringLiteral("org.freedesktop.Secret.Error.IsLocked");
|
||||
|
||||
static const auto DBUS_PATH_SECRETS = QStringLiteral("/org/freedesktop/secrets");
|
||||
|
||||
static const auto DBUS_PATH_TEMPLATE_ALIAS = QStringLiteral("%1/aliases/%2");
|
||||
static const auto DBUS_PATH_TEMPLATE_SESSION = QStringLiteral("%1/session/%2");
|
||||
static const auto DBUS_PATH_TEMPLATE_COLLECTION = QStringLiteral("%1/collection/%2");
|
||||
static const auto DBUS_PATH_TEMPLATE_ITEM = QStringLiteral("%1/%2");
|
||||
static const auto DBUS_PATH_TEMPLATE_PROMPT = QStringLiteral("%1/prompt/%2");
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSCONSTANTS_H
|
395
src/fdosecrets/dbus/DBusDispatch.cpp
Normal file
395
src/fdosecrets/dbus/DBusDispatch.cpp
Normal file
@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "DBusMgr.h"
|
||||
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
#include "fdosecrets/dbus/DBusTypes.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
#include <QDBusMetaType>
|
||||
#include <QThread>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
QString camelToPascal(const QString& camel)
|
||||
{
|
||||
if (camel.isEmpty()) {
|
||||
return camel;
|
||||
}
|
||||
return camel.at(0).toUpper() + camel.mid(1);
|
||||
}
|
||||
|
||||
bool prepareInputParams(const QVector<int>& inputTypes,
|
||||
const QVariantList& args,
|
||||
QVarLengthArray<void*, 10>& params,
|
||||
QVariantList& auxParams)
|
||||
{
|
||||
// prepare params
|
||||
for (int count = 0; count != inputTypes.size(); ++count) {
|
||||
const auto& id = inputTypes.at(count);
|
||||
const auto& arg = args.at(count);
|
||||
|
||||
if (arg.userType() == id) {
|
||||
// shortcut for no conversion
|
||||
params.append(const_cast<void*>(arg.constData()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// we need at least one conversion, allocate a slot in auxParams
|
||||
auxParams.append(QVariant(id, nullptr));
|
||||
auto& out = auxParams.last();
|
||||
// first handle QDBusArgument to wire types
|
||||
if (arg.userType() == qMetaTypeId<QDBusArgument>()) {
|
||||
auto wireId = typeToWireType(id).dbusTypeId;
|
||||
out = QVariant(wireId, nullptr);
|
||||
|
||||
const auto& in = arg.value<QDBusArgument>();
|
||||
if (!QDBusMetaType::demarshall(in, wireId, out.data())) {
|
||||
qDebug() << "Internal error: failed QDBusArgument conversion from" << arg << "to type"
|
||||
<< QMetaType::typeName(wireId) << wireId;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// make a copy to store the converted value
|
||||
out = arg;
|
||||
}
|
||||
// other conversions are handled here
|
||||
if (!out.convert(id)) {
|
||||
qDebug() << "Internal error: failed conversion from" << arg << "to type" << QMetaType::typeName(id)
|
||||
<< id;
|
||||
return false;
|
||||
}
|
||||
// good to go
|
||||
params.append(const_cast<void*>(out.constData()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBusMgr::populateMethodCache(const QMetaObject& mo)
|
||||
{
|
||||
for (int i = mo.methodOffset(); i != mo.methodCount(); ++i) {
|
||||
auto mm = mo.method(i);
|
||||
|
||||
// only register public Q_INVOKABLE methods
|
||||
if (mm.access() != QMetaMethod::Public || mm.methodType() != QMetaMethod::Method) {
|
||||
continue;
|
||||
}
|
||||
if (mm.returnType() != qMetaTypeId<DBusResult>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto iface = mo.classInfo(mo.indexOfClassInfo("D-Bus Interface")).value();
|
||||
if (!iface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// map from function name to dbus name
|
||||
auto member = camelToPascal(mm.name());
|
||||
// also "remove" => "Delete" due to c++ keyword restriction
|
||||
if (member == "Remove") {
|
||||
member = QStringLiteral("Delete");
|
||||
}
|
||||
auto cacheKey = QStringLiteral("%1.%2").arg(iface, member);
|
||||
|
||||
// skip if we already have it
|
||||
auto it = m_cachedMethods.find(cacheKey);
|
||||
if (it != m_cachedMethods.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MethodData md;
|
||||
md.isProperty = mm.tag() && mm.tag() == QStringLiteral("DBUS_PROPERTY");
|
||||
md.slotIdx = mm.methodIndex();
|
||||
|
||||
bool valid = true;
|
||||
// assumes output params (reference parameter) all follows input params
|
||||
bool outputBegin = false;
|
||||
for (const auto& paramType : mm.parameterTypes()) {
|
||||
auto id = QMetaType::type(paramType);
|
||||
|
||||
// handle the first optional calling client param
|
||||
if (id == qMetaTypeId<DBusClientPtr>()) {
|
||||
md.needsCallingClient = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle output types
|
||||
if (paramType.endsWith('&')) {
|
||||
outputBegin = true;
|
||||
id = QMetaType::type(paramType.left(paramType.length() - 1));
|
||||
md.outputTypes.append(id);
|
||||
auto paramData = typeToWireType(id);
|
||||
if (paramData.signature.isEmpty()) {
|
||||
qDebug() << "Internal error: unhandled new output type for dbus signature" << paramType;
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
md.outputTargetTypes.append(paramData.dbusTypeId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle input types
|
||||
if (outputBegin) {
|
||||
qDebug() << "Internal error: invalid method parameter order, no input parameter after output ones"
|
||||
<< mm.name();
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
auto sig = typeToWireType(id).signature;
|
||||
if (sig.isEmpty()) {
|
||||
qDebug() << "Internal error: unhandled new parameter type for dbus signature" << paramType;
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
md.inputTypes.append(id);
|
||||
md.signature += sig;
|
||||
}
|
||||
if (valid) {
|
||||
m_cachedMethods.insert(cacheKey, md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DBusMgr::handleMessage(const QDBusMessage& message, const QDBusConnection&)
|
||||
{
|
||||
// save a mutable copy of the message, as we may modify it to unify property access
|
||||
// and method call
|
||||
RequestedMethod req{
|
||||
message.interface(),
|
||||
message.member(),
|
||||
message.signature(),
|
||||
message.arguments(),
|
||||
RequestType::Method,
|
||||
};
|
||||
|
||||
if (req.interface == "org.freedesktop.DBus.Introspectable") {
|
||||
// introspection can be handled by Qt, just return false
|
||||
return false;
|
||||
} else if (req.interface == "org.freedesktop.DBus.Properties") {
|
||||
// but we need to handle properties ourselves like regular functions
|
||||
if (!rewriteRequestForProperty(req)) {
|
||||
// invalid message
|
||||
qDebug() << "Invalid message" << message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// who's calling?
|
||||
const auto& client = findClient(message.service());
|
||||
if (!client) {
|
||||
// the client already died
|
||||
return false;
|
||||
}
|
||||
|
||||
// activate the target object
|
||||
return activateObject(client, message.path(), req, message);
|
||||
}
|
||||
|
||||
bool DBusMgr::rewriteRequestForProperty(RequestedMethod& req)
|
||||
{
|
||||
if (req.member == "Set" && req.signature == "ssv") {
|
||||
// convert to normal method call: SetName
|
||||
req.interface = req.args.at(0).toString();
|
||||
req.member = req.member + req.args.at(1).toString();
|
||||
// unwrap the QDBusVariant and expose the inner signature
|
||||
auto arg = req.args.last().value<QDBusVariant>().variant();
|
||||
req.args = {arg};
|
||||
if (arg.userType() == qMetaTypeId<QDBusArgument>()) {
|
||||
req.signature = arg.value<QDBusArgument>().currentSignature();
|
||||
} else if (arg.userType() == QMetaType::QString) {
|
||||
req.signature = "s";
|
||||
} else {
|
||||
qDebug() << "Unhandled SetProperty value type" << QMetaType::typeName(arg.userType()) << arg.userType();
|
||||
return false;
|
||||
}
|
||||
} else if (req.member == "Get" && req.signature == "ss") {
|
||||
// convert to normal method call: Name
|
||||
req.interface = req.args.at(0).toString();
|
||||
req.member = req.args.at(1).toString();
|
||||
req.signature = "";
|
||||
req.args = {};
|
||||
req.type = RequestType::PropertyGet;
|
||||
} else if (req.member == "GetAll" && req.signature == "s") {
|
||||
// special handled in activateObject
|
||||
req.interface = req.args.at(0).toString();
|
||||
req.member = "";
|
||||
req.signature = "";
|
||||
req.args = {};
|
||||
req.type = RequestType::PropertyGetAll;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::activateObject(const DBusClientPtr& client,
|
||||
const QString& path,
|
||||
const RequestedMethod& req,
|
||||
const QDBusMessage& msg)
|
||||
{
|
||||
auto obj = m_objects.value(path, nullptr);
|
||||
if (!obj) {
|
||||
qDebug() << "DBusMgr::handleMessage with unknown path" << msg;
|
||||
return false;
|
||||
}
|
||||
Q_ASSERT_X(QThread::currentThread() == obj->thread(),
|
||||
"QDBusConnection: internal threading error",
|
||||
"function called for an object that is in another thread!!");
|
||||
|
||||
auto mo = obj->metaObject();
|
||||
// either interface matches, or interface is empty if req is property get all
|
||||
QString interface = mo->classInfo(mo->indexOfClassInfo("D-Bus Interface")).value();
|
||||
if (req.interface != interface && !(req.type == RequestType::PropertyGetAll && req.interface.isEmpty())) {
|
||||
qDebug() << "DBusMgr::handleMessage with mismatch interface" << msg;
|
||||
return false;
|
||||
}
|
||||
|
||||
// special handle of property getall
|
||||
if (req.type == RequestType::PropertyGetAll) {
|
||||
return objectPropertyGetAll(client, obj, interface, msg);
|
||||
}
|
||||
|
||||
// find the slot to call
|
||||
auto cacheKey = QStringLiteral("%1.%2").arg(req.interface, req.member);
|
||||
auto it = m_cachedMethods.find(cacheKey);
|
||||
if (it == m_cachedMethods.end()) {
|
||||
qDebug() << "DBusMgr::handleMessage with nonexisting method" << cacheKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
// requested signature is verified by Qt to match the content of arguments,
|
||||
// but this list of arguments itself is untrusted
|
||||
if (it->signature != req.signature || it->inputTypes.size() != req.args.size()) {
|
||||
qDebug() << "Message signature does not match, expected" << it->signature << it->inputTypes.size() << "got"
|
||||
<< req.signature << req.args.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
DBusResult ret;
|
||||
QVariantList outputArgs;
|
||||
if (!deliverMethod(client, obj, *it, req.args, ret, outputArgs)) {
|
||||
qDebug() << "Failed to deliver method" << msg;
|
||||
return sendDBus(msg.createErrorReply(QDBusError::InternalError, tr("Failed to deliver message")));
|
||||
}
|
||||
|
||||
if (!ret.ok()) {
|
||||
return sendDBus(msg.createErrorReply(ret, ""));
|
||||
}
|
||||
if (req.type == RequestType::PropertyGet) {
|
||||
// property get need the reply wrapped in QDBusVariant
|
||||
outputArgs[0] = QVariant::fromValue(QDBusVariant(outputArgs.first()));
|
||||
}
|
||||
return sendDBus(msg.createReply(outputArgs));
|
||||
}
|
||||
|
||||
bool DBusMgr::objectPropertyGetAll(const DBusClientPtr& client,
|
||||
DBusObject* obj,
|
||||
const QString& interface,
|
||||
const QDBusMessage& msg)
|
||||
{
|
||||
QVariantMap result;
|
||||
|
||||
// prefix match the cacheKey
|
||||
auto prefix = interface + ".";
|
||||
for (auto it = m_cachedMethods.constBegin(); it != m_cachedMethods.constEnd(); ++it) {
|
||||
if (!it.key().startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
if (!it.value().isProperty) {
|
||||
continue;
|
||||
}
|
||||
auto name = it.key().mid(prefix.size());
|
||||
|
||||
DBusResult ret;
|
||||
QVariantList outputArgs;
|
||||
if (!deliverMethod(client, obj, it.value(), {}, ret, outputArgs)) {
|
||||
// ignore any error per spec
|
||||
continue;
|
||||
}
|
||||
if (ret.err()) {
|
||||
// ignore any error per spec
|
||||
continue;
|
||||
}
|
||||
Q_ASSERT(outputArgs.size() == 1);
|
||||
|
||||
result.insert(name, outputArgs.first());
|
||||
}
|
||||
|
||||
return sendDBus(msg.createReply(QVariantList{result}));
|
||||
}
|
||||
|
||||
bool DBusMgr::deliverMethod(const DBusClientPtr& client,
|
||||
DBusObject* obj,
|
||||
const MethodData& method,
|
||||
const QVariantList& args,
|
||||
DBusResult& ret,
|
||||
QVariantList& outputArgs)
|
||||
{
|
||||
QVarLengthArray<void*, 10> params;
|
||||
QVariantList auxParams;
|
||||
|
||||
// the first one is for return type
|
||||
params.append(&ret);
|
||||
|
||||
if (method.needsCallingClient) {
|
||||
auxParams.append(QVariant::fromValue(client));
|
||||
params.append(const_cast<void*>(auxParams.last().constData()));
|
||||
}
|
||||
|
||||
// prepare input
|
||||
if (!prepareInputParams(method.inputTypes, args, params, auxParams)) {
|
||||
qDebug() << "Failed to prepare input params";
|
||||
return false;
|
||||
}
|
||||
|
||||
// prepare output args
|
||||
outputArgs.reserve(outputArgs.size() + method.outputTypes.size());
|
||||
for (const auto& outputType : asConst(method.outputTypes)) {
|
||||
outputArgs.append(QVariant(outputType, nullptr));
|
||||
params.append(const_cast<void*>(outputArgs.last().constData()));
|
||||
}
|
||||
|
||||
// call it
|
||||
bool fail = obj->qt_metacall(QMetaObject::InvokeMetaMethod, method.slotIdx, params.data()) >= 0;
|
||||
if (fail) {
|
||||
// generate internal error
|
||||
qWarning() << "Internal error: Failed to deliver message";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ret.ok()) {
|
||||
// error reply
|
||||
return true;
|
||||
}
|
||||
|
||||
// output args need to be converted before they can be directly sent out:
|
||||
for (int i = 0; i != outputArgs.size(); ++i) {
|
||||
auto& outputArg = outputArgs[i];
|
||||
if (!outputArg.convert(method.outputTargetTypes.at(i))) {
|
||||
qWarning() << "Internal error: Failed to convert message output to type"
|
||||
<< method.outputTargetTypes.at(i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace FdoSecrets
|
623
src/fdosecrets/dbus/DBusMgr.cpp
Normal file
623
src/fdosecrets/dbus/DBusMgr.cpp
Normal file
@ -0,0 +1,623 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "DBusMgr.h"
|
||||
|
||||
#include "fdosecrets/dbus/DBusConstants.h"
|
||||
#include "fdosecrets/dbus/DBusTypes.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
#include "core/Entry.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QThread>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
static const auto IntrospectionService = R"xml(
|
||||
<interface name="org.freedesktop.Secret.Service">
|
||||
<property name="Collections" type="ao" access="read"/>
|
||||
<signal name="CollectionCreated">
|
||||
<arg name="collection" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="CollectionDeleted">
|
||||
<arg name="collection" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="CollectionChanged">
|
||||
<arg name="collection" type="o" direction="out"/>
|
||||
</signal>
|
||||
<method name="OpenSession">
|
||||
<arg type="v" direction="out"/>
|
||||
<arg name="algorithm" type="s" direction="in"/>
|
||||
<arg name="input" type="v" direction="in"/>
|
||||
<arg name="result" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="CreateCollection">
|
||||
<arg type="o" direction="out"/>
|
||||
<arg name="properties" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||
<arg name="alias" type="s" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="SearchItems">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="attributes" type="a{ss}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="StringStringMap"/>
|
||||
<arg name="locked" type="ao" direction="out"/>
|
||||
</method>
|
||||
<method name="Unlock">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="paths" type="ao" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="Lock">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="paths" type="ao" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="GetSecrets">
|
||||
<arg type="a{o(oayays)}" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ObjectPathSecretMap"/>
|
||||
<arg name="items" type="ao" direction="in"/>
|
||||
<arg name="session" type="o" direction="in"/>
|
||||
</method>
|
||||
<method name="ReadAlias">
|
||||
<arg type="o" direction="out"/>
|
||||
<arg name="name" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="SetAlias">
|
||||
<arg name="name" type="s" direction="in"/>
|
||||
<arg name="collection" type="o" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
||||
)xml";
|
||||
|
||||
static const auto IntrospectionCollection = R"xml(
|
||||
<interface name="org.freedesktop.Secret.Collection">
|
||||
<property name="Items" type="ao" access="read"/>
|
||||
<property name="Label" type="s" access="readwrite"/>
|
||||
<property name="Locked" type="b" access="read"/>
|
||||
<property name="Created" type="t" access="read"/>
|
||||
<property name="Modified" type="t" access="read"/>
|
||||
<signal name="ItemCreated">
|
||||
<arg name="item" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ItemDeleted">
|
||||
<arg name="item" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ItemChanged">
|
||||
<arg name="item" type="o" direction="out"/>
|
||||
</signal>
|
||||
<method name="Delete">
|
||||
<arg type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="SearchItems">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="attributes" type="a{ss}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="StringStringMap"/>
|
||||
</method>
|
||||
<method name="CreateItem">
|
||||
<arg type="o" direction="out"/>
|
||||
<arg name="properties" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||
<arg name="secret" type="(oayays)" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="FdoSecrets::wire::Secret"/>
|
||||
<arg name="replace" type="b" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
)xml";
|
||||
|
||||
static const auto IntrospectionItem = R"xml(
|
||||
<interface name="org.freedesktop.Secret.Item">
|
||||
<property name="Locked" type="b" access="read"/>
|
||||
<property name="Attributes" type="a{ss}" access="readwrite">
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="StringStringMap"/>
|
||||
</property>
|
||||
<property name="Label" type="s" access="readwrite"/>
|
||||
<property name="Created" type="t" access="read"/>
|
||||
<property name="Modified" type="t" access="read"/>
|
||||
<method name="Delete">
|
||||
<arg type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="GetSecret">
|
||||
<arg type="(oayays)" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="FdoSecrets::wire::Secret"/>
|
||||
<arg name="session" type="o" direction="in"/>
|
||||
</method>
|
||||
<method name="SetSecret">
|
||||
<arg name="secret" type="(oayays)" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="FdoSecrets::wire::Secret"/>
|
||||
</method>
|
||||
</interface>
|
||||
)xml";
|
||||
|
||||
static const auto IntrospectionSession = R"xml(
|
||||
<interface name="org.freedesktop.Secret.Session">
|
||||
<method name="Close">
|
||||
</method>
|
||||
</interface>
|
||||
)xml";
|
||||
|
||||
static const auto IntrospectionPrompt = R"xml(
|
||||
<interface name="org.freedesktop.Secret.Prompt">
|
||||
<signal name="Completed">
|
||||
<arg name="dismissed" type="b" direction="out"/>
|
||||
<arg name="result" type="v" direction="out"/>
|
||||
</signal>
|
||||
<method name="Prompt">
|
||||
<arg name="windowId" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="Dismiss">
|
||||
</method>
|
||||
</interface>
|
||||
)xml";
|
||||
|
||||
DBusMgr::DBusMgr()
|
||||
: m_conn(QDBusConnection::sessionBus())
|
||||
{
|
||||
// remove client when it disappears on the bus
|
||||
m_watcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
|
||||
connect(&m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &DBusMgr::dbusServiceUnregistered);
|
||||
m_watcher.setConnection(m_conn);
|
||||
}
|
||||
|
||||
void DBusMgr::populateMethodCache()
|
||||
{
|
||||
// these are the methods we expose on DBus
|
||||
populateMethodCache(Service::staticMetaObject);
|
||||
populateMethodCache(Collection::staticMetaObject);
|
||||
populateMethodCache(Item::staticMetaObject);
|
||||
populateMethodCache(PromptBase::staticMetaObject);
|
||||
populateMethodCache(Session::staticMetaObject);
|
||||
}
|
||||
|
||||
DBusMgr::~DBusMgr() = default;
|
||||
|
||||
void DBusMgr::overrideClient(const DBusClientPtr& fake)
|
||||
{
|
||||
m_overrideClient = fake;
|
||||
}
|
||||
|
||||
QList<DBusClientPtr> DBusMgr::clients() const
|
||||
{
|
||||
return m_clients.values();
|
||||
}
|
||||
|
||||
bool DBusMgr::serviceInfo(const QString& addr, ProcessInfo& info) const
|
||||
{
|
||||
auto pid = m_conn.interface()->servicePid(addr);
|
||||
if (!pid.isValid()) {
|
||||
return false;
|
||||
}
|
||||
info.pid = pid.value();
|
||||
// The /proc/pid/exe link is more reliable than /proc/pid/cmdline
|
||||
// It's still weak and if the application does a prctl(PR_SET_DUMPABLE, 0) this link cannot be accessed.
|
||||
QFileInfo proc(QStringLiteral("/proc/%1/exe").arg(pid.value()));
|
||||
info.exePath = proc.canonicalFilePath();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::sendDBusSignal(const QString& path,
|
||||
const QString& interface,
|
||||
const QString& name,
|
||||
const QVariantList& arguments)
|
||||
{
|
||||
auto msg = QDBusMessage::createSignal(path, interface, name);
|
||||
msg.setArguments(arguments);
|
||||
return sendDBus(msg);
|
||||
}
|
||||
|
||||
bool DBusMgr::sendDBus(const QDBusMessage& reply)
|
||||
{
|
||||
bool ok = m_conn.send(reply);
|
||||
if (!ok) {
|
||||
qDebug() << "Failed to send on DBus:" << reply;
|
||||
emit error(tr("Failed to send reply on DBus"));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// `this` object is registered at multiple paths:
|
||||
// /org/freedesktop/secrets
|
||||
// /org/freedesktop/secrets/collection/xxx
|
||||
// /org/freedesktop/secrets/collection/xxx/yyy
|
||||
// /org/freedesktop/secrets/aliases/xxx
|
||||
// /org/freedesktop/secrets/session/xxx
|
||||
// /org/freedesktop/secrets/prompt/xxx
|
||||
//
|
||||
// The path validation is left to Qt, this method only do the minimum
|
||||
// required to differentiate the paths.
|
||||
DBusMgr::ParsedPath DBusMgr::parsePath(const QString& path)
|
||||
{
|
||||
Q_ASSERT(path.startsWith('/'));
|
||||
Q_ASSERT(path == "/" || !path.endsWith('/'));
|
||||
|
||||
static const QString DBusPathSecrets = DBUS_PATH_SECRETS;
|
||||
|
||||
if (!path.startsWith(DBusPathSecrets)) {
|
||||
return ParsedPath{};
|
||||
}
|
||||
auto parts = path.mid(DBusPathSecrets.size()).split('/');
|
||||
// the first part is always empty
|
||||
if (parts.isEmpty() || parts.first() != "") {
|
||||
return ParsedPath{};
|
||||
}
|
||||
parts.takeFirst();
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return ParsedPath{PathType::Service};
|
||||
} else if (parts.size() == 2) {
|
||||
if (parts.at(0) == "collection") {
|
||||
return ParsedPath{PathType::Collection, parts.at(1)};
|
||||
} else if (parts.at(0) == "aliases") {
|
||||
return ParsedPath{PathType::Aliases, parts.at(1)};
|
||||
} else if (parts.at(0) == "prompt") {
|
||||
return ParsedPath{PathType::Prompt, parts.at(1)};
|
||||
} else if (parts.at(0) == "session") {
|
||||
return ParsedPath{PathType::Session, parts.at(1)};
|
||||
}
|
||||
} else if (parts.size() == 3) {
|
||||
if (parts.at(0) == "collection") {
|
||||
return ParsedPath{PathType::Item, parts.at(2), parts.at(1)};
|
||||
}
|
||||
}
|
||||
return ParsedPath{};
|
||||
}
|
||||
|
||||
QString DBusMgr::introspect(const QString& path) const
|
||||
{
|
||||
auto parsed = parsePath(path);
|
||||
switch (parsed.type) {
|
||||
case PathType::Service:
|
||||
return IntrospectionService;
|
||||
case PathType::Collection:
|
||||
case PathType::Aliases:
|
||||
return IntrospectionCollection;
|
||||
case PathType::Prompt:
|
||||
return IntrospectionPrompt;
|
||||
case PathType::Session:
|
||||
return IntrospectionSession;
|
||||
case PathType::Item:
|
||||
return IntrospectionItem;
|
||||
case PathType::Unknown:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool DBusMgr::serviceOccupied() const
|
||||
{
|
||||
auto reply = m_conn.interface()->isServiceRegistered(DBUS_SERVICE_SECRET);
|
||||
if (!reply.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (reply.value()) {
|
||||
auto pid = m_conn.interface()->servicePid(DBUS_SERVICE_SECRET);
|
||||
if (pid.isValid() && pid.value() != qApp->applicationPid()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DBusMgr::reportExistingService() const
|
||||
{
|
||||
auto pidStr = tr("Unknown", "Unknown PID");
|
||||
auto exeStr = tr("Unknown", "Unknown executable path");
|
||||
|
||||
ProcessInfo info{};
|
||||
if (serviceInfo(DBUS_SERVICE_SECRET, info)) {
|
||||
pidStr = QString::number(info.pid);
|
||||
if (!info.exePath.isEmpty()) {
|
||||
exeStr = info.exePath;
|
||||
}
|
||||
}
|
||||
|
||||
auto otherService = tr("<i>PID: %1, Executable: %2</i>", "<i>PID: 1234, Executable: /path/to/exe</i>")
|
||||
.arg(pidStr, exeStr.toHtmlEscaped());
|
||||
return tr("Another secret service is running (%1).<br/>"
|
||||
"Please stop/remove it before re-enabling the Secret Service Integration.")
|
||||
.arg(otherService);
|
||||
}
|
||||
|
||||
bool DBusMgr::registerObject(const QString& path, DBusObject* obj, bool primary)
|
||||
{
|
||||
if (!m_conn.registerVirtualObject(path, this)) {
|
||||
qDebug() << "failed to register" << obj << "at" << path;
|
||||
return false;
|
||||
}
|
||||
connect(obj, &DBusObject::destroyed, this, &DBusMgr::unregisterObject);
|
||||
m_objects.insert(path, obj);
|
||||
if (primary) {
|
||||
obj->setObjectPath(path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::registerObject(Service* service)
|
||||
{
|
||||
if (!m_conn.registerService(DBUS_SERVICE_SECRET)) {
|
||||
const auto existing = reportExistingService();
|
||||
qDebug() << "Failed to register DBus service at " << DBUS_SERVICE_SECRET;
|
||||
qDebug() << existing;
|
||||
emit error(tr("Failed to register DBus service at %1.<br/>").arg(DBUS_SERVICE_SECRET) + existing);
|
||||
return false;
|
||||
}
|
||||
connect(service, &DBusObject::destroyed, this, [this]() { m_conn.unregisterService(DBUS_SERVICE_SECRET); });
|
||||
|
||||
if (!registerObject(DBUS_PATH_SECRETS, service)) {
|
||||
qDebug() << "Failed to register service on DBus at path" << DBUS_PATH_SECRETS;
|
||||
emit error(tr("Failed to register service on DBus at path '%1'").arg(DBUS_PATH_SECRETS));
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(service, &Service::collectionCreated, this, &DBusMgr::emitCollectionCreated);
|
||||
connect(service, &Service::collectionChanged, this, &DBusMgr::emitCollectionChanged);
|
||||
connect(service, &Service::collectionDeleted, this, &DBusMgr::emitCollectionDeleted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::registerObject(Collection* coll)
|
||||
{
|
||||
auto name = encodePath(coll->name());
|
||||
auto path = DBUS_PATH_TEMPLATE_COLLECTION.arg(DBUS_PATH_SECRETS, name);
|
||||
if (!registerObject(path, coll)) {
|
||||
// try again with a suffix
|
||||
name.append(QString("_%1").arg(Tools::uuidToHex(QUuid::createUuid()).left(4)));
|
||||
path = DBUS_PATH_TEMPLATE_COLLECTION.arg(DBUS_PATH_SECRETS, name);
|
||||
|
||||
if (!registerObject(path, coll)) {
|
||||
qDebug() << "Failed to register database on DBus under name" << name;
|
||||
emit error(tr("Failed to register database on DBus under the name '%1'").arg(name));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
connect(coll, &Collection::itemCreated, this, &DBusMgr::emitItemCreated);
|
||||
connect(coll, &Collection::itemChanged, this, &DBusMgr::emitItemChanged);
|
||||
connect(coll, &Collection::itemDeleted, this, &DBusMgr::emitItemDeleted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::registerObject(Session* sess)
|
||||
{
|
||||
auto path = DBUS_PATH_TEMPLATE_SESSION.arg(DBUS_PATH_SECRETS, sess->id());
|
||||
if (!registerObject(path, sess)) {
|
||||
emit error(tr("Failed to register session on DBus at path '%1'").arg(path));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::registerObject(Item* item)
|
||||
{
|
||||
auto path = DBUS_PATH_TEMPLATE_ITEM.arg(item->collection()->objectPath().path(), item->backend()->uuidToHex());
|
||||
if (!registerObject(path, item)) {
|
||||
emit error(tr("Failed to register item on DBus at path '%1'").arg(path));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBusMgr::registerObject(PromptBase* prompt)
|
||||
{
|
||||
auto path = DBUS_PATH_TEMPLATE_PROMPT.arg(DBUS_PATH_SECRETS, Tools::uuidToHex(QUuid::createUuid()));
|
||||
if (!registerObject(path, prompt)) {
|
||||
emit error(tr("Failed to register prompt object on DBus at path '%1'").arg(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(prompt, &PromptBase::completed, this, &DBusMgr::emitPromptCompleted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBusMgr::unregisterObject(DBusObject* obj)
|
||||
{
|
||||
auto count = m_objects.remove(obj->objectPath().path());
|
||||
if (count > 0) {
|
||||
m_conn.unregisterObject(obj->objectPath().path());
|
||||
obj->setObjectPath("/");
|
||||
}
|
||||
}
|
||||
|
||||
bool DBusMgr::registerAlias(Collection* coll, const QString& alias)
|
||||
{
|
||||
auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias);
|
||||
if (!registerObject(path, coll, false)) {
|
||||
qDebug() << "Failed to register database on DBus under alias" << alias;
|
||||
// usually this is reported back directly on dbus, so no need to show in UI
|
||||
return false;
|
||||
}
|
||||
// alias signals are handled together with collections' primary path in emitCollection*
|
||||
// but we need to handle object destroy here
|
||||
connect(coll, &DBusObject::destroyed, this, [this, alias]() { unregisterAlias(alias); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBusMgr::unregisterAlias(const QString& alias)
|
||||
{
|
||||
auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias);
|
||||
// DBusMgr::unregisterObject only handles primary path
|
||||
m_objects.remove(path);
|
||||
m_conn.unregisterObject(path);
|
||||
}
|
||||
|
||||
void DBusMgr::emitCollectionCreated(Collection* coll)
|
||||
{
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(coll->objectPath());
|
||||
sendDBusSignal(DBUS_PATH_SECRETS, DBUS_INTERFACE_SECRET_SERVICE, QStringLiteral("CollectionCreated"), args);
|
||||
}
|
||||
|
||||
void DBusMgr::emitCollectionChanged(Collection* coll)
|
||||
{
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(coll->objectPath());
|
||||
sendDBusSignal(DBUS_PATH_SECRETS, DBUS_INTERFACE_SECRET_SERVICE, "CollectionChanged", args);
|
||||
}
|
||||
|
||||
void DBusMgr::emitCollectionDeleted(Collection* coll)
|
||||
{
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(coll->objectPath());
|
||||
sendDBusSignal(DBUS_PATH_SECRETS, DBUS_INTERFACE_SECRET_SERVICE, QStringLiteral("CollectionDeleted"), args);
|
||||
}
|
||||
|
||||
void DBusMgr::emitItemCreated(Item* item)
|
||||
{
|
||||
auto coll = item->collection();
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(item->objectPath());
|
||||
// send on primary path
|
||||
sendDBusSignal(
|
||||
coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemCreated"), args);
|
||||
// also send on all alias path
|
||||
for (const auto& alias : coll->aliases()) {
|
||||
auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias);
|
||||
sendDBusSignal(path, DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemCreated"), args);
|
||||
}
|
||||
}
|
||||
|
||||
void DBusMgr::emitItemChanged(Item* item)
|
||||
{
|
||||
auto coll = item->collection();
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(item->objectPath());
|
||||
// send on primary path
|
||||
sendDBusSignal(
|
||||
coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemChanged"), args);
|
||||
// also send on all alias path
|
||||
for (const auto& alias : coll->aliases()) {
|
||||
auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias);
|
||||
sendDBusSignal(path, DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemChanged"), args);
|
||||
}
|
||||
}
|
||||
|
||||
void DBusMgr::emitItemDeleted(Item* item)
|
||||
{
|
||||
auto coll = item->collection();
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(item->objectPath());
|
||||
// send on primary path
|
||||
sendDBusSignal(
|
||||
coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemDeleted"), args);
|
||||
// also send on all alias path
|
||||
for (const auto& alias : coll->aliases()) {
|
||||
auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias);
|
||||
sendDBusSignal(path, DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemDeleted"), args);
|
||||
}
|
||||
}
|
||||
|
||||
void DBusMgr::emitPromptCompleted(bool dismissed, QVariant result)
|
||||
{
|
||||
auto prompt = qobject_cast<PromptBase*>(sender());
|
||||
if (!prompt) {
|
||||
qDebug() << "Wrong sender in emitPromptCompleted";
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the result contains a valid value, otherwise QDBusVariant refuses to marshall it.
|
||||
if (!result.isValid()) {
|
||||
result = QString{};
|
||||
}
|
||||
|
||||
QVariantList args;
|
||||
args += QVariant::fromValue(dismissed);
|
||||
args += QVariant::fromValue(QDBusVariant(result));
|
||||
sendDBusSignal(prompt->objectPath().path(), DBUS_INTERFACE_SECRET_PROMPT, QStringLiteral("Completed"), args);
|
||||
}
|
||||
|
||||
DBusClientPtr DBusMgr::findClient(const QString& addr)
|
||||
{
|
||||
if (m_overrideClient) {
|
||||
return m_overrideClient;
|
||||
}
|
||||
|
||||
auto it = m_clients.find(addr);
|
||||
if (it == m_clients.end()) {
|
||||
auto client = createClient(addr);
|
||||
if (!client) {
|
||||
return {};
|
||||
}
|
||||
it = m_clients.insert(addr, client);
|
||||
}
|
||||
// double check the client
|
||||
ProcessInfo info{};
|
||||
if (!serviceInfo(addr, info) || info.pid != it.value()->pid()) {
|
||||
dbusServiceUnregistered(addr);
|
||||
return {};
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
DBusClientPtr DBusMgr::createClient(const QString& addr)
|
||||
{
|
||||
ProcessInfo info{};
|
||||
if (!serviceInfo(addr, info)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto client = DBusClientPtr(new DBusClient(this, addr, info.pid, info.exePath.isEmpty() ? addr : info.exePath));
|
||||
|
||||
emit clientConnected(client);
|
||||
m_watcher.addWatchedService(addr);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
void DBusMgr::removeClient(DBusClient* client)
|
||||
{
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = m_clients.find(client->address());
|
||||
if (it == m_clients.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit clientDisconnected(*it);
|
||||
m_clients.erase(it);
|
||||
}
|
||||
|
||||
void DBusMgr::dbusServiceUnregistered(const QString& service)
|
||||
{
|
||||
auto removed = m_watcher.removeWatchedService(service);
|
||||
if (!removed) {
|
||||
qDebug("FdoSecrets: Failed to remove service watcher");
|
||||
}
|
||||
|
||||
auto it = m_clients.find(service);
|
||||
if (it == m_clients.end()) {
|
||||
return;
|
||||
}
|
||||
auto client = it.value();
|
||||
|
||||
client->disconnectDBus();
|
||||
}
|
||||
} // namespace FdoSecrets
|
335
src/fdosecrets/dbus/DBusMgr.h
Normal file
335
src/fdosecrets/dbus/DBusMgr.h
Normal file
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSMGR_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSMGR_H
|
||||
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QDBusVirtualObject>
|
||||
#include <QDebug>
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
|
||||
#include <utility>
|
||||
|
||||
class TestFdoSecrets;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Collection;
|
||||
class Service;
|
||||
class PromptBase;
|
||||
class Session;
|
||||
class Item;
|
||||
class DBusObject;
|
||||
class DBusResult;
|
||||
|
||||
/**
|
||||
* DBusMgr takes care of the interaction between dbus and business logic objects (DBusObject). It handles the
|
||||
* following
|
||||
* - Registering/unregistering service name
|
||||
* - Registering/unregistering paths
|
||||
* - Relay signals from DBusObject to dbus
|
||||
* - Manage per-client states, mapping from dbus caller address to Client
|
||||
* - Deliver method calls from dbus to DBusObject
|
||||
*
|
||||
* Special note in implementation of method delivery:
|
||||
* There are two sets of vocabulary classes in use for method delivery.
|
||||
* The Qt DBus system uses QDBusVariant/QDBusObjectPath and other primitive types in QDBusMessage::arguments(),
|
||||
* i.e. the on-the-wire types.
|
||||
* The DBusObject invokable methods uses QVariant/DBusObject* and other primitive types in parameters (parameter
|
||||
* types). FdoSecrets::typeToWireType establishes the mapping from parameter types to on-the-wire types. The
|
||||
* conversion between types is done with the help of QMetaType convert.
|
||||
*
|
||||
* The method delivery sequence:
|
||||
* - DBusMgr::handleMessage unifies method call and property access into the same form
|
||||
* - DBusMgr::activateObject finds the target object and calls the method by doing the following
|
||||
* * check the object exists and the interface matches
|
||||
* * find the cached method information MethodData
|
||||
* * DBusMgr::prepareInputParams check and convert input arguments in QDBusMessage::arguments() to types expected
|
||||
* by DBusObject
|
||||
* * prepare output argument storage
|
||||
* * call the method
|
||||
* * convert types to what Qt DBus expects
|
||||
*
|
||||
* The MethodData is pre-computed using Qt meta object system by finding methods with signature matching a certain
|
||||
* pattern:
|
||||
* Q_INVOKABLE DBusResult methodName(const DBusClientPtr& client,
|
||||
* const X& input1,
|
||||
* const Y& input2,
|
||||
* Z& output1,
|
||||
* ZZ& output2)
|
||||
* Note that the first parameter of client is optional.
|
||||
*/
|
||||
class DBusMgr : public QDBusVirtualObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DBusMgr();
|
||||
|
||||
/**
|
||||
* @brief Must be called after all dbus types are registered
|
||||
*/
|
||||
void populateMethodCache();
|
||||
|
||||
~DBusMgr() override;
|
||||
|
||||
QString introspect(const QString& path) const override;
|
||||
bool handleMessage(const QDBusMessage& message, const QDBusConnection& connection) override;
|
||||
|
||||
/**
|
||||
* @return current connected clients
|
||||
*/
|
||||
QList<DBusClientPtr> clients() const;
|
||||
|
||||
/**
|
||||
* @return whether the org.freedesktop.secrets service is owned by others
|
||||
*/
|
||||
bool serviceOccupied() const;
|
||||
|
||||
/**
|
||||
* Check the running secret service and return info about it
|
||||
* @return html string suitable to be shown in the UI
|
||||
*/
|
||||
QString reportExistingService() const;
|
||||
|
||||
// expose on dbus and handle signals
|
||||
bool registerObject(Service* service);
|
||||
bool registerObject(Collection* coll);
|
||||
bool registerObject(Session* sess);
|
||||
bool registerObject(Item* item);
|
||||
bool registerObject(PromptBase* prompt);
|
||||
|
||||
void unregisterObject(DBusObject* obj);
|
||||
|
||||
// and the signals are handled together with collection's primary path
|
||||
bool registerAlias(Collection* coll, const QString& alias);
|
||||
void unregisterAlias(const QString& alias);
|
||||
|
||||
/**
|
||||
* Return the object path of the pointed DBusObject, or "/" if the pointer is null
|
||||
* @tparam T
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
template <typename T> static QDBusObjectPath objectPathSafe(T* object)
|
||||
{
|
||||
if (object) {
|
||||
return object->objectPath();
|
||||
}
|
||||
return QDBusObjectPath(QStringLiteral("/"));
|
||||
}
|
||||
template <typename T> static QDBusObjectPath objectPathSafe(QPointer<T> object)
|
||||
{
|
||||
return objectPathSafe(object.data());
|
||||
}
|
||||
static QDBusObjectPath objectPathSafe(std::nullptr_t)
|
||||
{
|
||||
return QDBusObjectPath(QStringLiteral("/"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of DBusObjects to object path
|
||||
* @tparam T
|
||||
* @param objects
|
||||
* @return
|
||||
*/
|
||||
template <typename T> static QList<QDBusObjectPath> objectsToPath(QList<T*> objects)
|
||||
{
|
||||
QList<QDBusObjectPath> res;
|
||||
res.reserve(objects.size());
|
||||
for (auto object : objects) {
|
||||
res.append(objectPathSafe(object));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an object path to a pointer of the object
|
||||
* @tparam T
|
||||
* @param path
|
||||
* @return the pointer of the object, or nullptr if path is "/"
|
||||
*/
|
||||
template <typename T> T* pathToObject(const QDBusObjectPath& path) const
|
||||
{
|
||||
if (path.path() == QStringLiteral("/")) {
|
||||
return nullptr;
|
||||
}
|
||||
auto obj = qobject_cast<T*>(m_objects.value(path.path(), nullptr));
|
||||
if (!obj) {
|
||||
qDebug() << "object not found at path" << path.path();
|
||||
qDebug() << m_objects;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of object paths to a list of objects.
|
||||
* "/" paths (i.e. nullptrs) will be skipped in the resulting list
|
||||
* @tparam T
|
||||
* @param paths
|
||||
* @return
|
||||
*/
|
||||
template <typename T> QList<T*> pathsToObject(const QList<QDBusObjectPath>& paths) const
|
||||
{
|
||||
QList<T*> res;
|
||||
res.reserve(paths.size());
|
||||
for (const auto& path : paths) {
|
||||
auto object = pathToObject<T>(path);
|
||||
if (object) {
|
||||
res.append(object);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Force client to be a specific object, used for testing
|
||||
void overrideClient(const DBusClientPtr& fake);
|
||||
|
||||
signals:
|
||||
void clientConnected(const DBusClientPtr& client);
|
||||
void clientDisconnected(const DBusClientPtr& client);
|
||||
void error(const QString& msg);
|
||||
|
||||
private slots:
|
||||
void emitCollectionCreated(Collection* coll);
|
||||
void emitCollectionChanged(Collection* coll);
|
||||
void emitCollectionDeleted(Collection* coll);
|
||||
void emitItemCreated(Item* item);
|
||||
void emitItemChanged(Item* item);
|
||||
void emitItemDeleted(Item* item);
|
||||
void emitPromptCompleted(bool dismissed, QVariant result);
|
||||
|
||||
void dbusServiceUnregistered(const QString& service);
|
||||
|
||||
private:
|
||||
QDBusConnection m_conn;
|
||||
|
||||
struct ProcessInfo
|
||||
{
|
||||
uint pid;
|
||||
QString exePath;
|
||||
};
|
||||
bool serviceInfo(const QString& addr, ProcessInfo& info) const;
|
||||
|
||||
bool sendDBusSignal(const QString& path,
|
||||
const QString& interface,
|
||||
const QString& name,
|
||||
const QVariantList& arguments);
|
||||
bool sendDBus(const QDBusMessage& reply);
|
||||
|
||||
// object path registration
|
||||
QHash<QString, QPointer<DBusObject>> m_objects{};
|
||||
enum class PathType
|
||||
{
|
||||
Service,
|
||||
Collection,
|
||||
Aliases,
|
||||
Prompt,
|
||||
Session,
|
||||
Item,
|
||||
Unknown,
|
||||
};
|
||||
struct ParsedPath
|
||||
{
|
||||
PathType type;
|
||||
QString id;
|
||||
// only used when type == Item
|
||||
QString parentId;
|
||||
explicit ParsedPath(PathType type = PathType::Unknown, QString id = "", QString parentId = "")
|
||||
: type(type)
|
||||
, id(std::move(id))
|
||||
, parentId(std::move(parentId))
|
||||
{
|
||||
}
|
||||
};
|
||||
static ParsedPath parsePath(const QString& path);
|
||||
bool registerObject(const QString& path, DBusObject* obj, bool primary = true);
|
||||
|
||||
// method dispatching
|
||||
struct MethodData
|
||||
{
|
||||
int slotIdx{-1};
|
||||
QByteArray signature{};
|
||||
QVector<int> inputTypes{};
|
||||
QVector<int> outputTypes{};
|
||||
QVector<int> outputTargetTypes{};
|
||||
bool isProperty{false};
|
||||
bool needsCallingClient{false};
|
||||
};
|
||||
QHash<QString, MethodData> m_cachedMethods{};
|
||||
void populateMethodCache(const QMetaObject& mo);
|
||||
|
||||
enum class RequestType
|
||||
{
|
||||
Method,
|
||||
PropertyGet,
|
||||
PropertyGetAll,
|
||||
};
|
||||
struct RequestedMethod
|
||||
{
|
||||
QString interface;
|
||||
QString member;
|
||||
QString signature;
|
||||
QVariantList args;
|
||||
RequestType type;
|
||||
};
|
||||
static bool rewriteRequestForProperty(RequestedMethod& req);
|
||||
bool activateObject(const DBusClientPtr& client,
|
||||
const QString& path,
|
||||
const RequestedMethod& req,
|
||||
const QDBusMessage& msg);
|
||||
bool objectPropertyGetAll(const DBusClientPtr& client,
|
||||
DBusObject* obj,
|
||||
const QString& interface,
|
||||
const QDBusMessage& msg);
|
||||
static bool deliverMethod(const DBusClientPtr& client,
|
||||
DBusObject* obj,
|
||||
const MethodData& method,
|
||||
const QVariantList& args,
|
||||
DBusResult& ret,
|
||||
QVariantList& outputArgs);
|
||||
|
||||
// client management
|
||||
friend class DBusClient;
|
||||
|
||||
DBusClientPtr findClient(const QString& addr);
|
||||
DBusClientPtr createClient(const QString& addr);
|
||||
|
||||
/**
|
||||
* @brief This gets called from DBusClient::disconnectDBus
|
||||
* @param client
|
||||
*/
|
||||
void removeClient(DBusClient* client);
|
||||
|
||||
QDBusServiceWatcher m_watcher{};
|
||||
// mapping from the unique dbus peer address to client object
|
||||
QHash<QString, DBusClientPtr> m_clients{};
|
||||
|
||||
DBusClientPtr m_overrideClient;
|
||||
|
||||
friend class ::TestFdoSecrets;
|
||||
};
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSMGR_H
|
@ -19,37 +19,32 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
DBusObject::DBusObject(DBusObject* parent)
|
||||
: QObject(parent)
|
||||
, m_dbusAdaptor(nullptr)
|
||||
, m_dbus(parent->dbus())
|
||||
{
|
||||
}
|
||||
|
||||
bool DBusObject::registerWithPath(const QString& path, bool primary)
|
||||
DBusObject::DBusObject(QSharedPointer<DBusMgr> dbus)
|
||||
: QObject(nullptr)
|
||||
, m_objectPath("/")
|
||||
, m_dbus(std::move(dbus))
|
||||
{
|
||||
if (primary) {
|
||||
m_objectPath.setPath(path);
|
||||
}
|
||||
|
||||
return QDBusConnection::sessionBus().registerObject(path, this);
|
||||
}
|
||||
|
||||
QString DBusObject::callingPeerName() const
|
||||
DBusObject::~DBusObject()
|
||||
{
|
||||
auto pid = callingPeerPid();
|
||||
QFile proc(QStringLiteral("/proc/%1/comm").arg(pid));
|
||||
if (!proc.open(QFile::ReadOnly)) {
|
||||
return callingPeer();
|
||||
}
|
||||
QTextStream stream(&proc);
|
||||
return stream.readAll().trimmed();
|
||||
emit destroyed(this);
|
||||
}
|
||||
|
||||
void DBusObject::setObjectPath(const QString& path)
|
||||
{
|
||||
m_objectPath.setPath(path);
|
||||
}
|
||||
|
||||
QString encodePath(const QString& value)
|
130
src/fdosecrets/dbus/DBusObject.h
Normal file
130
src/fdosecrets/dbus/DBusObject.h
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSOBJECT_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSOBJECT_H
|
||||
|
||||
#include "DBusConstants.h"
|
||||
#include "DBusMgr.h"
|
||||
#include "DBusTypes.h"
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusContext>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMetaProperty>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
// define the tag text as empty, so the compiler doesn't see it
|
||||
#define DBUS_PROPERTY
|
||||
#endif // #ifndef Q_MOC_RUN
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Service;
|
||||
|
||||
/**
|
||||
* @brief A common base class for all dbus-exposed objects.
|
||||
*/
|
||||
class DBusObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
~DBusObject() override;
|
||||
|
||||
const QDBusObjectPath& objectPath() const
|
||||
{
|
||||
return m_objectPath;
|
||||
}
|
||||
|
||||
const QSharedPointer<DBusMgr>& dbus() const
|
||||
{
|
||||
return m_dbus;
|
||||
}
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Necessary because by the time QObject::destroyed is emitted,
|
||||
* we already lost any info in DBusObject
|
||||
*/
|
||||
void destroyed(DBusObject* self);
|
||||
|
||||
protected:
|
||||
explicit DBusObject(DBusObject* parent);
|
||||
explicit DBusObject(QSharedPointer<DBusMgr> dbus);
|
||||
|
||||
private:
|
||||
friend class DBusMgr;
|
||||
void setObjectPath(const QString& path);
|
||||
|
||||
QDBusObjectPath m_objectPath;
|
||||
QSharedPointer<DBusMgr> m_dbus;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A dbus error or not
|
||||
*/
|
||||
class DBusResult : public QString
|
||||
{
|
||||
public:
|
||||
DBusResult() = default;
|
||||
explicit DBusResult(QString error)
|
||||
: QString(std::move(error))
|
||||
{
|
||||
}
|
||||
|
||||
// Implicitly convert from QDBusError
|
||||
DBusResult(QDBusError::ErrorType error) // NOLINT(google-explicit-constructor)
|
||||
: QString(QDBusError::errorString(error))
|
||||
{
|
||||
}
|
||||
|
||||
bool ok() const
|
||||
{
|
||||
return isEmpty();
|
||||
}
|
||||
bool err() const
|
||||
{
|
||||
return !isEmpty();
|
||||
}
|
||||
void okOrDie() const
|
||||
{
|
||||
Q_ASSERT(ok());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode the string value to a DBus object path safe representation,
|
||||
* using a schema similar to URI encoding, but with percentage(%) replaced with
|
||||
* underscore(_). All characters except [A-Za-z0-9] are encoded. For non-ascii
|
||||
* characters, UTF-8 encoding is first applied and each of the resulting byte
|
||||
* value is encoded.
|
||||
* @param value
|
||||
* @return encoded string
|
||||
*/
|
||||
QString encodePath(const QString& value);
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
Q_DECLARE_METATYPE(FdoSecrets::DBusResult);
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSOBJECT_H
|
219
src/fdosecrets/dbus/DBusTypes.cpp
Normal file
219
src/fdosecrets/dbus/DBusTypes.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
* Copyright 2010, Michael Leupold <lemma@confuego.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 or (at your option)
|
||||
* 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 "DBusTypes.h"
|
||||
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
#include <QDBusMetaType>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
bool inherits(const QMetaObject* derived, const QMetaObject* base)
|
||||
{
|
||||
for (auto super = derived; super; super = super->superClass()) {
|
||||
if (super == base) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T> void registerConverter(const QWeakPointer<DBusMgr>& weak)
|
||||
{
|
||||
// from parameter type to on-the-wire type
|
||||
QMetaType::registerConverter<T*, QDBusObjectPath>([](const T* obj) { return DBusMgr::objectPathSafe(obj); });
|
||||
QMetaType::registerConverter<QList<T*>, QList<QDBusObjectPath>>(
|
||||
[](const QList<T*> objs) { return DBusMgr::objectsToPath(objs); });
|
||||
|
||||
// the opposite
|
||||
QMetaType::registerConverter<QDBusObjectPath, T*>([weak](const QDBusObjectPath& path) -> T* {
|
||||
if (auto dbus = weak.lock()) {
|
||||
return dbus->pathToObject<T>(path);
|
||||
}
|
||||
qDebug() << "No DBusMgr when looking up path" << path.path();
|
||||
return nullptr;
|
||||
});
|
||||
QMetaType::registerConverter<QList<QDBusObjectPath>, QList<T*>>([weak](const QList<QDBusObjectPath>& paths) {
|
||||
if (auto dbus = weak.lock()) {
|
||||
return dbus->pathsToObject<T>(paths);
|
||||
}
|
||||
qDebug() << "No DBusMgr when looking up paths";
|
||||
return QList<T*>{};
|
||||
});
|
||||
}
|
||||
|
||||
void registerDBusTypes(const QSharedPointer<DBusMgr>& dbus)
|
||||
{
|
||||
// On the wire types:
|
||||
// - various primary types
|
||||
// - QDBusVariant
|
||||
// - wire::Secret
|
||||
// - wire::ObjectPathSecretMap
|
||||
// - QDBusObjectPath
|
||||
// - QList<QDBusObjectPath>
|
||||
|
||||
// Parameter types:
|
||||
// - various primary types
|
||||
// - QVariant
|
||||
// - Secret
|
||||
// - ObjectSecretMap
|
||||
// - DBusObject* (and derived classes)
|
||||
// - QList<DBusObject*>
|
||||
|
||||
// NOTE: when registering, in additional to the class' fully qualified name,
|
||||
// the partial-namespace/non-namespace name should also be registered as alias
|
||||
// otherwise all those usages in Q_INVOKABLE methods without FQN won't be included
|
||||
// in the meta type system.
|
||||
#define REG_METATYPE(type) \
|
||||
qRegisterMetaType<type>(); \
|
||||
qRegisterMetaType<type>(#type)
|
||||
|
||||
// register on-the-wire types
|
||||
// Qt container types for builtin types don't need registration
|
||||
REG_METATYPE(wire::Secret);
|
||||
REG_METATYPE(wire::StringStringMap);
|
||||
REG_METATYPE(wire::ObjectPathSecretMap);
|
||||
|
||||
qDBusRegisterMetaType<wire::Secret>();
|
||||
qDBusRegisterMetaType<wire::StringStringMap>();
|
||||
qDBusRegisterMetaType<wire::ObjectPathSecretMap>();
|
||||
|
||||
// register parameter types
|
||||
REG_METATYPE(Secret);
|
||||
REG_METATYPE(StringStringMap);
|
||||
REG_METATYPE(ItemSecretMap);
|
||||
REG_METATYPE(DBusResult);
|
||||
REG_METATYPE(DBusClientPtr);
|
||||
|
||||
#define REG_DBUS_OBJ(name) \
|
||||
REG_METATYPE(name*); \
|
||||
REG_METATYPE(QList<name*>)
|
||||
REG_DBUS_OBJ(DBusObject);
|
||||
REG_DBUS_OBJ(Service);
|
||||
REG_DBUS_OBJ(Collection);
|
||||
REG_DBUS_OBJ(Item);
|
||||
REG_DBUS_OBJ(Session);
|
||||
REG_DBUS_OBJ(PromptBase);
|
||||
#undef REG_DBUS_OBJ
|
||||
|
||||
#undef REG_METATYPE
|
||||
|
||||
QWeakPointer<DBusMgr> weak = dbus;
|
||||
// register converter between on-the-wire types and parameter types
|
||||
// some pairs are missing because that particular direction isn't used
|
||||
registerConverter<DBusObject>(weak);
|
||||
registerConverter<Service>(weak);
|
||||
registerConverter<Collection>(weak);
|
||||
registerConverter<Item>(weak);
|
||||
registerConverter<Session>(weak);
|
||||
registerConverter<PromptBase>(weak);
|
||||
|
||||
QMetaType::registerConverter<wire::Secret, Secret>(
|
||||
[weak](const wire::Secret& from) { return from.unmarshal(weak); });
|
||||
QMetaType::registerConverter(&Secret::marshal);
|
||||
|
||||
QMetaType::registerConverter<ItemSecretMap, wire::ObjectPathSecretMap>([](const ItemSecretMap& map) {
|
||||
wire::ObjectPathSecretMap ret;
|
||||
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
|
||||
ret.insert(it.key()->objectPath(), it.value().marshal());
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
QMetaType::registerConverter<QDBusVariant, QVariant>([](const QDBusVariant& obj) { return obj.variant(); });
|
||||
QMetaType::registerConverter<QVariant, QDBusVariant>([](const QVariant& obj) { return QDBusVariant(obj); });
|
||||
|
||||
// structural types are received as QDBusArgument,
|
||||
// top level QDBusArgument in method parameters are directly handled
|
||||
// in prepareInputParams.
|
||||
// But in Collection::createItem, we need to convert a inner QDBusArgument to StringStringMap
|
||||
QMetaType::registerConverter<QDBusArgument, StringStringMap>([](const QDBusArgument& arg) {
|
||||
if (arg.currentSignature() != "a{ss}") {
|
||||
return StringStringMap{};
|
||||
}
|
||||
// QDBusArgument is COW and qdbus_cast modifies it by detaching even it is const.
|
||||
// we don't want to modify the instance (arg) stored in the qvariant so we create a copy
|
||||
const auto copy = arg; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
return qdbus_cast<StringStringMap>(copy);
|
||||
});
|
||||
}
|
||||
|
||||
ParamData typeToWireType(int id)
|
||||
{
|
||||
switch (id) {
|
||||
case QMetaType::QString:
|
||||
return {QByteArrayLiteral("s"), QMetaType::QString};
|
||||
case QMetaType::QVariant:
|
||||
return {QByteArrayLiteral("v"), qMetaTypeId<QDBusVariant>()};
|
||||
case QMetaType::QVariantMap:
|
||||
return {QByteArrayLiteral("a{sv}"), QMetaType::QVariantMap};
|
||||
case QMetaType::Bool:
|
||||
return {QByteArrayLiteral("b"), QMetaType::Bool};
|
||||
case QMetaType::ULongLong:
|
||||
return {QByteArrayLiteral("t"), QMetaType::ULongLong};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (id == qMetaTypeId<StringStringMap>()) {
|
||||
return {QByteArrayLiteral("a{ss}"), qMetaTypeId<wire::StringStringMap>()};
|
||||
} else if (id == qMetaTypeId<ItemSecretMap>()) {
|
||||
return {QByteArrayLiteral("a{o(oayays)}"), qMetaTypeId<wire::ObjectPathSecretMap>()};
|
||||
} else if (id == qMetaTypeId<Secret>()) {
|
||||
return {QByteArrayLiteral("(oayays)"), qMetaTypeId<wire::Secret>()};
|
||||
} else if (id == qMetaTypeId<DBusObject*>()) {
|
||||
return {QByteArrayLiteral("o"), qMetaTypeId<QDBusObjectPath>()};
|
||||
} else if (id == qMetaTypeId<QList<DBusObject*>>()) {
|
||||
return {QByteArrayLiteral("ao"), qMetaTypeId<QList<QDBusObjectPath>>()};
|
||||
}
|
||||
|
||||
QMetaType mt(id);
|
||||
if (!mt.isValid()) {
|
||||
return {};
|
||||
}
|
||||
if (QByteArray(QMetaType::typeName(id)).startsWith("QList")) {
|
||||
// QList<Object*>
|
||||
return {QByteArrayLiteral("ao"), qMetaTypeId<QList<QDBusObjectPath>>()};
|
||||
}
|
||||
if (!inherits(mt.metaObject(), &DBusObject::staticMetaObject)) {
|
||||
return {};
|
||||
}
|
||||
// DBusObjects
|
||||
return {QByteArrayLiteral("o"), qMetaTypeId<QDBusObjectPath>()};
|
||||
}
|
||||
|
||||
::FdoSecrets::Secret wire::Secret::unmarshal(const QWeakPointer<DBusMgr>& weak) const
|
||||
{
|
||||
if (auto dbus = weak.lock()) {
|
||||
return {dbus->pathToObject<Session>(session), parameters, value, contentType};
|
||||
}
|
||||
qDebug() << "No DBusMgr when converting wire::Secret";
|
||||
return {nullptr, parameters, value, contentType};
|
||||
}
|
||||
|
||||
wire::Secret Secret::marshal() const
|
||||
{
|
||||
return {DBusMgr::objectPathSafe(session), parameters, value, contentType};
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
107
src/fdosecrets/dbus/DBusTypes.h
Normal file
107
src/fdosecrets/dbus/DBusTypes.h
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
* Copyright 2010, Michael Leupold <lemma@confuego.org>
|
||||
* Copyright 2010-2011, Valentin Rusu <valir@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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSTYPES_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSTYPES_H
|
||||
|
||||
#include <QDBusArgument>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
struct Secret;
|
||||
class DBusMgr;
|
||||
|
||||
// types used directly in Qt DBus system
|
||||
namespace wire
|
||||
{
|
||||
struct Secret
|
||||
{
|
||||
QDBusObjectPath session;
|
||||
QByteArray parameters;
|
||||
QByteArray value;
|
||||
QString contentType;
|
||||
|
||||
::FdoSecrets::Secret unmarshal(const QWeakPointer<DBusMgr>& weak) const;
|
||||
};
|
||||
|
||||
inline QDBusArgument& operator<<(QDBusArgument& argument, const Secret& secret)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << secret.session << secret.parameters << secret.value << secret.contentType;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline const QDBusArgument& operator>>(const QDBusArgument& argument, Secret& secret)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> secret.session >> secret.parameters >> secret.value >> secret.contentType;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
using StringStringMap = QMap<QString, QString>;
|
||||
using ObjectPathSecretMap = QMap<QDBusObjectPath, Secret>;
|
||||
} // namespace wire
|
||||
|
||||
// types used in method parameters
|
||||
class Session;
|
||||
class Item;
|
||||
struct Secret
|
||||
{
|
||||
const Session* session;
|
||||
QByteArray parameters;
|
||||
QByteArray value;
|
||||
QString contentType;
|
||||
|
||||
wire::Secret marshal() const;
|
||||
};
|
||||
using wire::StringStringMap;
|
||||
using ItemSecretMap = QHash<Item*, Secret>;
|
||||
|
||||
/**
|
||||
* Register the types needed for the fd.o Secrets D-Bus interface.
|
||||
*/
|
||||
void registerDBusTypes(const QSharedPointer<DBusMgr>& dbus);
|
||||
|
||||
struct ParamData
|
||||
{
|
||||
QByteArray signature;
|
||||
int dbusTypeId;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert parameter type to on-the-wire type and associated dbus signature.
|
||||
* This is NOT a generic version, and only handles types used in org.freedesktop.secrets
|
||||
* @param id
|
||||
* @return ParamData
|
||||
*/
|
||||
ParamData typeToWireType(int id);
|
||||
} // namespace FdoSecrets
|
||||
|
||||
Q_DECLARE_METATYPE(FdoSecrets::wire::Secret)
|
||||
Q_DECLARE_METATYPE(FdoSecrets::wire::StringStringMap);
|
||||
Q_DECLARE_METATYPE(FdoSecrets::wire::ObjectPathSecretMap);
|
||||
|
||||
Q_DECLARE_METATYPE(FdoSecrets::Secret)
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSTYPES_H
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
@ -40,7 +41,7 @@ namespace FdoSecrets
|
||||
}
|
||||
|
||||
Collection::Collection(Service* parent, DatabaseWidget* backend)
|
||||
: DBusObjectHelper(parent)
|
||||
: DBusObject(parent)
|
||||
, m_backend(backend)
|
||||
, m_exposedGroup(nullptr)
|
||||
{
|
||||
@ -72,23 +73,14 @@ namespace FdoSecrets
|
||||
m_items.first()->doDelete();
|
||||
}
|
||||
cleanupConnections();
|
||||
unregisterPrimaryPath();
|
||||
dbus()->unregisterObject(this);
|
||||
|
||||
// make sure we have updated copy of the filepath, which is used to identify the database.
|
||||
m_backendPath = m_backend->database()->canonicalFilePath();
|
||||
|
||||
// register the object, handling potentially duplicated name
|
||||
auto name = encodePath(this->name());
|
||||
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_COLLECTION).arg(p()->objectPath().path(), name);
|
||||
if (!registerWithPath(path)) {
|
||||
// try again with a suffix
|
||||
name += QStringLiteral("_%1").arg(Tools::uuidToHex(QUuid::createUuid()).left(4));
|
||||
path = QStringLiteral(DBUS_PATH_TEMPLATE_COLLECTION).arg(p()->objectPath().path(), name);
|
||||
|
||||
if (!registerWithPath(path)) {
|
||||
service()->plugin()->emitError(tr("Failed to register database on DBus under the name '%1'").arg(name));
|
||||
return false;
|
||||
}
|
||||
if (!dbus()->registerObject(this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// populate contents after expose on dbus, because items rely on parent's dbus object path
|
||||
@ -98,6 +90,7 @@ namespace FdoSecrets
|
||||
cleanupConnections();
|
||||
}
|
||||
|
||||
emit collectionChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -108,52 +101,55 @@ namespace FdoSecrets
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::ensureBackend() const
|
||||
DBusResult Collection::ensureBackend() const
|
||||
{
|
||||
if (!m_backend) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::ensureUnlocked() const
|
||||
DBusResult Collection::ensureUnlocked() const
|
||||
{
|
||||
if (backendLocked()) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_IS_LOCKED));
|
||||
return DBusResult(DBUS_ERROR_SECRET_IS_LOCKED);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Item*>> Collection::items() const
|
||||
DBusResult Collection::items(QList<Item*>& items) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
return m_items;
|
||||
items = m_items;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<QString> Collection::label() const
|
||||
DBusResult Collection::label(QString& label) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (backendLocked()) {
|
||||
return name();
|
||||
label = name();
|
||||
} else {
|
||||
label = m_backend->database()->metadata()->name();
|
||||
}
|
||||
return m_backend->database()->metadata()->name();
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::setLabel(const QString& label)
|
||||
DBusResult Collection::setLabel(const QString& label)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -161,82 +157,87 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<bool> Collection::locked() const
|
||||
DBusResult Collection::locked(bool& locked) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
return backendLocked();
|
||||
locked = backendLocked();
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Collection::created() const
|
||||
DBusResult Collection::created(qulonglong& created) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
return static_cast<qulonglong>(m_backend->database()->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch()
|
||||
/ 1000);
|
||||
created = static_cast<qulonglong>(
|
||||
m_backend->database()->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Collection::modified() const
|
||||
DBusResult Collection::modified(qulonglong& modified) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
// FIXME: there seems not to have a global modified time.
|
||||
// Use a more accurate time, considering all metadata, group, entry.
|
||||
return static_cast<qulonglong>(
|
||||
modified = static_cast<qulonglong>(
|
||||
m_backend->database()->rootGroup()->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000);
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<PromptBase*> Collection::deleteCollection()
|
||||
DBusResult Collection::remove(const DBusClientPtr& client, PromptBase*& prompt)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Delete means close database
|
||||
auto dpret = DeleteCollectionPrompt::Create(service(), this);
|
||||
if (dpret.isError()) {
|
||||
return dpret;
|
||||
prompt = PromptBase::Create<DeleteCollectionPrompt>(service(), this);
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
auto prompt = dpret.value();
|
||||
if (backendLocked()) {
|
||||
// this won't raise a dialog, immediate execute
|
||||
auto pret = prompt->prompt({});
|
||||
if (pret.isError()) {
|
||||
return pret;
|
||||
ret = prompt->prompt(client, {});
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
prompt = nullptr;
|
||||
}
|
||||
// defer the close to the prompt
|
||||
return prompt;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Item*>> Collection::searchItems(const StringStringMap& attributes)
|
||||
DBusResult Collection::searchItems(const StringStringMap& attributes, QList<Item*>& items)
|
||||
{
|
||||
items.clear();
|
||||
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
// searchItems should work, whether `this` is locked or not.
|
||||
// however, we can't search items the same way as in gnome-keying,
|
||||
// because there's no database at all when locked.
|
||||
return QList<Item*>{};
|
||||
return {};
|
||||
}
|
||||
|
||||
// shortcut logic for Uuid/Path attributes, as they can uniquely identify an item.
|
||||
@ -244,20 +245,18 @@ namespace FdoSecrets
|
||||
auto uuid = QUuid::fromRfc4122(QByteArray::fromHex(attributes.value(ItemAttributes::UuidKey).toLatin1()));
|
||||
auto entry = m_exposedGroup->findEntryByUuid(uuid);
|
||||
if (entry) {
|
||||
return QList<Item*>{m_entryToItem.value(entry)};
|
||||
} else {
|
||||
return QList<Item*>{};
|
||||
items += m_entryToItem.value(entry);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (attributes.contains(ItemAttributes::PathKey)) {
|
||||
auto path = attributes.value(ItemAttributes::PathKey);
|
||||
auto entry = m_exposedGroup->findEntryByPath(path);
|
||||
if (entry) {
|
||||
return QList<Item*>{m_entryToItem.value(entry)};
|
||||
} else {
|
||||
return QList<Item*>{};
|
||||
items += m_entryToItem.value(entry);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<EntrySearcher::SearchTerm> terms;
|
||||
@ -265,13 +264,12 @@ namespace FdoSecrets
|
||||
terms << attributeToTerm(it.key(), it.value());
|
||||
}
|
||||
|
||||
QList<Item*> items;
|
||||
const auto foundEntries = EntrySearcher(false, true).search(terms, m_exposedGroup);
|
||||
items.reserve(foundEntries.size());
|
||||
for (const auto& entry : foundEntries) {
|
||||
items << m_entryToItem.value(entry);
|
||||
}
|
||||
return items;
|
||||
return {};
|
||||
}
|
||||
|
||||
EntrySearcher::SearchTerm Collection::attributeToTerm(const QString& key, const QString& value)
|
||||
@ -296,99 +294,58 @@ namespace FdoSecrets
|
||||
return term;
|
||||
}
|
||||
|
||||
DBusReturn<Item*>
|
||||
Collection::createItem(const QVariantMap& properties, const SecretStruct& secret, bool replace, PromptBase*& prompt)
|
||||
DBusResult Collection::createItem(const QVariantMap& properties,
|
||||
const Secret& secret,
|
||||
bool replace,
|
||||
Item*& item,
|
||||
PromptBase*& prompt)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!pathToObject<Session>(secret.session)) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
}
|
||||
|
||||
prompt = nullptr;
|
||||
|
||||
bool newlyCreated = true;
|
||||
Item* item = nullptr;
|
||||
item = nullptr;
|
||||
QString itemPath;
|
||||
StringStringMap attributes;
|
||||
|
||||
auto iterAttr = properties.find(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes"));
|
||||
auto iterAttr = properties.find(DBUS_INTERFACE_SECRET_ITEM + ".Attributes");
|
||||
if (iterAttr != properties.end()) {
|
||||
attributes = iterAttr.value().value<StringStringMap>();
|
||||
// the actual value in iterAttr.value() is QDBusArgument, which represents a structure
|
||||
// and qt has no idea what this corresponds to.
|
||||
// we thus force a conversion to StringStringMap here. The conversion is registered in
|
||||
// DBusTypes.cpp
|
||||
auto attributes = iterAttr.value().value<StringStringMap>();
|
||||
|
||||
itemPath = attributes.value(ItemAttributes::PathKey);
|
||||
|
||||
// check existing item using attributes
|
||||
auto existing = searchItems(attributes);
|
||||
if (existing.isError()) {
|
||||
return existing;
|
||||
QList<Item*> existing;
|
||||
ret = searchItems(attributes, existing);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (!existing.value().isEmpty() && replace) {
|
||||
item = existing.value().front();
|
||||
newlyCreated = false;
|
||||
if (!existing.isEmpty() && replace) {
|
||||
item = existing.front();
|
||||
}
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
// normalize itemPath
|
||||
itemPath = itemPath.startsWith('/') ? QString{} : QStringLiteral("/") + itemPath;
|
||||
|
||||
// split itemPath to groupPath and itemName
|
||||
auto components = itemPath.split('/');
|
||||
Q_ASSERT(components.size() >= 2);
|
||||
|
||||
auto itemName = components.takeLast();
|
||||
Group* group = findCreateGroupByPath(components.join('/'));
|
||||
|
||||
// create new Entry in backend
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle(itemName);
|
||||
entry->setUsername(m_backend->database()->metadata()->defaultUserName());
|
||||
group->applyGroupIconOnCreateTo(entry);
|
||||
|
||||
entry->setGroup(group);
|
||||
|
||||
// when creation finishes in backend, we will already have item
|
||||
item = m_entryToItem.value(entry, nullptr);
|
||||
|
||||
if (!item) {
|
||||
// may happen if entry somehow ends up in recycle bin
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
}
|
||||
prompt = PromptBase::Create<CreateItemPrompt>(service(), this, properties, secret, itemPath, item);
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
|
||||
ret = item->setProperties(properties);
|
||||
if (ret.isError()) {
|
||||
if (newlyCreated) {
|
||||
item->doDelete();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ret = item->setSecret(secret);
|
||||
if (ret.isError()) {
|
||||
if (newlyCreated) {
|
||||
item->doDelete();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
return item;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::setProperties(const QVariantMap& properties)
|
||||
DBusResult Collection::setProperties(const QVariantMap& properties)
|
||||
{
|
||||
auto label = properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_COLLECTION ".Label")).toString();
|
||||
auto label = properties.value(DBUS_INTERFACE_SECRET_COLLECTION + ".Label").toString();
|
||||
|
||||
auto ret = setLabel(label);
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -400,10 +357,10 @@ namespace FdoSecrets
|
||||
return m_aliases;
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::addAlias(QString alias)
|
||||
DBusResult Collection::addAlias(QString alias)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -415,22 +372,20 @@ namespace FdoSecrets
|
||||
|
||||
emit aliasAboutToAdd(alias);
|
||||
|
||||
bool ok =
|
||||
registerWithPath(QStringLiteral(DBUS_PATH_TEMPLATE_ALIAS).arg(p()->objectPath().path(), alias), false);
|
||||
if (ok) {
|
||||
if (dbus()->registerAlias(this, alias)) {
|
||||
m_aliases.insert(alias);
|
||||
emit aliasAdded(alias);
|
||||
} else {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
return QDBusError::InvalidObjectPath;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::removeAlias(QString alias)
|
||||
DBusResult Collection::removeAlias(QString alias)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -440,9 +395,7 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
QDBusConnection::sessionBus().unregisterObject(
|
||||
QStringLiteral(DBUS_PATH_TEMPLATE_ALIAS).arg(p()->objectPath().path(), alias));
|
||||
|
||||
dbus()->unregisterAlias(alias);
|
||||
m_aliases.remove(alias);
|
||||
emit aliasRemoved(alias);
|
||||
|
||||
@ -470,14 +423,11 @@ namespace FdoSecrets
|
||||
|
||||
void Collection::onDatabaseLockChanged()
|
||||
{
|
||||
auto locked = backendLocked();
|
||||
if (!locked) {
|
||||
populateContents();
|
||||
} else {
|
||||
cleanupConnections();
|
||||
if (!reloadBackend()) {
|
||||
doDelete();
|
||||
return;
|
||||
}
|
||||
emit collectionLockChanged(locked);
|
||||
emit collectionChanged();
|
||||
emit collectionLockChanged(backendLocked());
|
||||
}
|
||||
|
||||
void Collection::populateContents()
|
||||
@ -550,6 +500,8 @@ namespace FdoSecrets
|
||||
onEntryAdded(entry, false);
|
||||
}
|
||||
|
||||
// Do not connect to databaseModified signal because we only want signals for the subset under m_exposedGroup
|
||||
connect(m_backend->database()->metadata(), &Metadata::metadataModified, this, &Collection::collectionChanged);
|
||||
connectGroupSignalRecursive(m_exposedGroup);
|
||||
}
|
||||
|
||||
@ -641,7 +593,8 @@ namespace FdoSecrets
|
||||
|
||||
emit collectionAboutToDelete();
|
||||
|
||||
unregisterPrimaryPath();
|
||||
// remove from dbus early
|
||||
dbus()->unregisterObject(this);
|
||||
|
||||
// remove alias manually to trigger signal
|
||||
for (const auto& a : aliases()) {
|
||||
@ -692,7 +645,7 @@ namespace FdoSecrets
|
||||
|
||||
void Collection::doDeleteEntries(QList<Entry*> entries)
|
||||
{
|
||||
m_backend->deleteEntries(std::move(entries));
|
||||
m_backend->deleteEntries(std::move(entries), FdoSecrets::settings()->confirmDeleteItem());
|
||||
}
|
||||
|
||||
Group* Collection::findCreateGroupByPath(const QString& groupPath)
|
||||
@ -748,4 +701,36 @@ namespace FdoSecrets
|
||||
return inRecycleBin(entry->group());
|
||||
}
|
||||
|
||||
Item* Collection::doNewItem(const DBusClientPtr& client, QString itemPath)
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
|
||||
// normalize itemPath
|
||||
itemPath = (itemPath.startsWith('/') ? QString{} : QStringLiteral("/")) + itemPath;
|
||||
|
||||
// split itemPath to groupPath and itemName
|
||||
auto components = itemPath.split('/');
|
||||
Q_ASSERT(components.size() >= 2);
|
||||
|
||||
auto itemName = components.takeLast();
|
||||
Group* group = findCreateGroupByPath(components.join('/'));
|
||||
|
||||
// create new Entry in backend
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle(itemName);
|
||||
entry->setUsername(m_backend->database()->metadata()->defaultUserName());
|
||||
group->applyGroupIconOnCreateTo(entry);
|
||||
|
||||
entry->setGroup(group);
|
||||
|
||||
// the item was just created so there is no point in having it not authorized
|
||||
client->setItemAuthorized(entry->uuid(), AuthDecision::Allowed);
|
||||
|
||||
// when creation finishes in backend, we will already have item
|
||||
auto created = m_entryToItem.value(entry, nullptr);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
@ -18,9 +18,9 @@
|
||||
#ifndef KEEPASSXC_FDOSECRETS_COLLECTION_H
|
||||
#define KEEPASSXC_FDOSECRETS_COLLECTION_H
|
||||
|
||||
#include "DBusObject.h"
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
|
||||
#include "adaptors/CollectionAdaptor.h"
|
||||
#include "core/EntrySearcher.h"
|
||||
|
||||
#include <QPointer>
|
||||
@ -36,9 +36,10 @@ namespace FdoSecrets
|
||||
class Item;
|
||||
class PromptBase;
|
||||
class Service;
|
||||
class Collection : public DBusObjectHelper<Collection, CollectionAdaptor>
|
||||
class Collection : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_COLLECTION_LITERAL)
|
||||
|
||||
explicit Collection(Service* parent, DatabaseWidget* backend);
|
||||
|
||||
@ -54,21 +55,21 @@ namespace FdoSecrets
|
||||
*/
|
||||
static Collection* Create(Service* parent, DatabaseWidget* backend);
|
||||
|
||||
DBusReturn<const QList<Item*>> items() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult items(QList<Item*>& items) const;
|
||||
|
||||
DBusReturn<QString> label() const;
|
||||
DBusReturn<void> setLabel(const QString& label);
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult label(QString& label) const;
|
||||
Q_INVOKABLE DBusResult setLabel(const QString& label);
|
||||
|
||||
DBusReturn<bool> locked() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult locked(bool& locked) const;
|
||||
|
||||
DBusReturn<qulonglong> created() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult created(qulonglong& created) const;
|
||||
|
||||
DBusReturn<qulonglong> modified() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult modified(qulonglong& modified) const;
|
||||
|
||||
DBusReturn<PromptBase*> deleteCollection();
|
||||
DBusReturn<const QList<Item*>> searchItems(const StringStringMap& attributes);
|
||||
DBusReturn<Item*>
|
||||
createItem(const QVariantMap& properties, const SecretStruct& secret, bool replace, PromptBase*& prompt);
|
||||
Q_INVOKABLE DBusResult remove(const DBusClientPtr& client, PromptBase*& prompt);
|
||||
Q_INVOKABLE DBusResult searchItems(const StringStringMap& attributes, QList<Item*>& items);
|
||||
Q_INVOKABLE DBusResult
|
||||
createItem(const QVariantMap& properties, const Secret& secret, bool replace, Item*& item, PromptBase*& prompt);
|
||||
|
||||
signals:
|
||||
void itemCreated(Item* item);
|
||||
@ -86,15 +87,15 @@ namespace FdoSecrets
|
||||
void doneUnlockCollection(bool accepted);
|
||||
|
||||
public:
|
||||
DBusReturn<void> setProperties(const QVariantMap& properties);
|
||||
DBusResult setProperties(const QVariantMap& properties);
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return backend();
|
||||
}
|
||||
|
||||
DBusReturn<void> removeAlias(QString alias);
|
||||
DBusReturn<void> addAlias(QString alias);
|
||||
DBusResult removeAlias(QString alias);
|
||||
DBusResult addAlias(QString alias);
|
||||
const QSet<QString> aliases() const;
|
||||
|
||||
/**
|
||||
@ -116,6 +117,7 @@ namespace FdoSecrets
|
||||
// expose some methods for Prompt to use
|
||||
bool doLock();
|
||||
void doUnlock();
|
||||
Item* doNewItem(const DBusClientPtr& client, QString itemPath);
|
||||
// will remove self
|
||||
void doDelete();
|
||||
|
||||
@ -147,13 +149,13 @@ namespace FdoSecrets
|
||||
* Check if the backend is a valid object, send error reply if not.
|
||||
* @return true if the backend is valid.
|
||||
*/
|
||||
DBusReturn<void> ensureBackend() const;
|
||||
DBusResult ensureBackend() const;
|
||||
|
||||
/**
|
||||
* Ensure the database is unlocked, send error reply if locked.
|
||||
* @return true if the database is locked
|
||||
*/
|
||||
DBusReturn<void> ensureUnlocked() const;
|
||||
DBusResult ensureUnlocked() const;
|
||||
|
||||
/**
|
||||
* Like mkdir -p, find or create the group by path, under m_exposedGroup
|
||||
|
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSOBJECT_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSOBJECT_H
|
||||
|
||||
#include "fdosecrets/objects/DBusReturn.h"
|
||||
#include "fdosecrets/objects/DBusTypes.h"
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusContext>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMetaProperty>
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Service;
|
||||
|
||||
/**
|
||||
* @brief A common base class for all dbus-exposed objects.
|
||||
* However, derived class should inherit from `DBusObjectHelper`, which is
|
||||
* the only way to set DBus adaptor and enforces correct adaptor creation.
|
||||
*/
|
||||
class DBusObject : public QObject, public QDBusContext
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
const QDBusObjectPath& objectPath() const
|
||||
{
|
||||
return m_objectPath;
|
||||
}
|
||||
|
||||
QDBusAbstractAdaptor& dbusAdaptor() const
|
||||
{
|
||||
return *m_dbusAdaptor;
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Register this object at given DBus path
|
||||
* @param path DBus path to register at
|
||||
* @param primary whether this path to be considered primary. The primary path is the one to be returned by
|
||||
* `DBusObject::objectPath`.
|
||||
* @return true on success
|
||||
*/
|
||||
bool registerWithPath(const QString& path, bool primary = true);
|
||||
|
||||
void unregisterPrimaryPath()
|
||||
{
|
||||
if (m_objectPath.path() == QStringLiteral("/")) {
|
||||
return;
|
||||
}
|
||||
QDBusConnection::sessionBus().unregisterObject(m_objectPath.path());
|
||||
m_objectPath.setPath(QStringLiteral("/"));
|
||||
}
|
||||
|
||||
QString callingPeer() const
|
||||
{
|
||||
Q_ASSERT(calledFromDBus());
|
||||
return message().service();
|
||||
}
|
||||
|
||||
uint callingPeerPid() const
|
||||
{
|
||||
return connection().interface()->servicePid(callingPeer());
|
||||
}
|
||||
|
||||
QString callingPeerName() const;
|
||||
|
||||
DBusObject* p() const
|
||||
{
|
||||
return qobject_cast<DBusObject*>(parent());
|
||||
}
|
||||
|
||||
private:
|
||||
explicit DBusObject(DBusObject* parent);
|
||||
|
||||
/**
|
||||
* Derived class should not directly use sendErrorReply.
|
||||
* Instead, use raiseError
|
||||
*/
|
||||
using QDBusContext::sendErrorReply;
|
||||
|
||||
template <typename U> friend class DBusReturn;
|
||||
template <typename Object, typename Adaptor> friend class DBusObjectHelper;
|
||||
|
||||
QDBusAbstractAdaptor* m_dbusAdaptor;
|
||||
QDBusObjectPath m_objectPath;
|
||||
};
|
||||
|
||||
template <typename Object, typename Adaptor> class DBusObjectHelper : public DBusObject
|
||||
{
|
||||
protected:
|
||||
explicit DBusObjectHelper(DBusObject* parent)
|
||||
: DBusObject(parent)
|
||||
{
|
||||
// creating new Adaptor has to be delayed into constructor's body,
|
||||
// and can't be simply moved to initializer list, because at that
|
||||
// point the base QObject class hasn't been initialized and will sigfault.
|
||||
m_dbusAdaptor = new Adaptor(static_cast<Object*>(this));
|
||||
m_dbusAdaptor->setParent(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the object path of the pointed DBusObject, or "/" if the pointer is null
|
||||
* @tparam T
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
template <typename T> QDBusObjectPath objectPathSafe(T* object)
|
||||
{
|
||||
if (object) {
|
||||
return object->objectPath();
|
||||
}
|
||||
return QDBusObjectPath(QStringLiteral("/"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of DBusObjects to object path
|
||||
* @tparam T
|
||||
* @param objects
|
||||
* @return
|
||||
*/
|
||||
template <typename T> QList<QDBusObjectPath> objectsToPath(QList<T*> objects)
|
||||
{
|
||||
QList<QDBusObjectPath> res;
|
||||
res.reserve(objects.size());
|
||||
for (auto object : objects) {
|
||||
res.append(objectPathSafe(object));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an object path to a pointer of the object
|
||||
* @tparam T
|
||||
* @param path
|
||||
* @return the pointer of the object, or nullptr if path is "/"
|
||||
*/
|
||||
template <typename T> T* pathToObject(const QDBusObjectPath& path)
|
||||
{
|
||||
if (path.path() == QStringLiteral("/")) {
|
||||
return nullptr;
|
||||
}
|
||||
return qobject_cast<T*>(QDBusConnection::sessionBus().objectRegisteredAt(path.path()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of object paths to a list of objects.
|
||||
* "/" paths (i.e. nullptrs) will be skipped in the resulting list
|
||||
* @tparam T
|
||||
* @param paths
|
||||
* @return
|
||||
*/
|
||||
template <typename T> QList<T*> pathsToObject(const QList<QDBusObjectPath>& paths)
|
||||
{
|
||||
QList<T*> res;
|
||||
res.reserve(paths.size());
|
||||
for (const auto& path : paths) {
|
||||
auto object = pathToObject<T>(path);
|
||||
if (object) {
|
||||
res.append(object);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the string value to a DBus object path safe representation,
|
||||
* using a schema similar to URI encoding, but with percentage(%) replaced with
|
||||
* underscore(_). All characters except [A-Za-z0-9] are encoded. For non-ascii
|
||||
* characters, UTF-8 encoding is first applied and each of the resulting byte
|
||||
* value is encoded.
|
||||
* @param value
|
||||
* @return encoded string
|
||||
*/
|
||||
QString encodePath(const QString& value);
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSOBJECT_H
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "DBusReturn.h"
|
@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSRETURN_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSRETURN_H
|
||||
|
||||
#include <QDBusError>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
namespace details
|
||||
{
|
||||
class DBusReturnImpl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Check if this object contains an error
|
||||
* @return true if it contains an error, false otherwise.
|
||||
*/
|
||||
bool isError() const
|
||||
{
|
||||
return !m_errorName.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error name
|
||||
* @return
|
||||
*/
|
||||
QString errorName() const
|
||||
{
|
||||
return m_errorName;
|
||||
}
|
||||
|
||||
void okOrDie() const
|
||||
{
|
||||
Q_ASSERT(!isError());
|
||||
}
|
||||
|
||||
protected:
|
||||
struct WithErrorTag
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct from an error
|
||||
* @param errorName
|
||||
* @param value
|
||||
*/
|
||||
DBusReturnImpl(QString errorName, WithErrorTag)
|
||||
: m_errorName(std::move(errorName))
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturnImpl() = default;
|
||||
|
||||
protected:
|
||||
QString m_errorName;
|
||||
};
|
||||
} // namespace details
|
||||
|
||||
/**
|
||||
* Either a return value or a DBus error
|
||||
* @tparam T
|
||||
*/
|
||||
template <typename T = void> class DBusReturn : public details::DBusReturnImpl
|
||||
{
|
||||
protected:
|
||||
using DBusReturnImpl::DBusReturnImpl;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
DBusReturn() = default;
|
||||
|
||||
/**
|
||||
* Implicitly construct from a value
|
||||
* @param value
|
||||
*/
|
||||
DBusReturn(T&& value) // NOLINT(google-explicit-constructor)
|
||||
: m_value(std::move(value))
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn(const T& value) // NOLINT(google-explicit-constructor)
|
||||
: m_value(std::move(value))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Implicitly convert from another error of different value type.
|
||||
*
|
||||
* @tparam U must not be the same as T
|
||||
* @param other
|
||||
*/
|
||||
template <typename U, typename = typename std::enable_if<!std::is_same<T, U>::value>::type>
|
||||
DBusReturn(const DBusReturn<U>& other) // NOLINT(google-explicit-constructor)
|
||||
: DBusReturn(other.errorName(), DBusReturnImpl::WithErrorTag{})
|
||||
{
|
||||
Q_ASSERT(other.isError());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from error
|
||||
* @param errorType
|
||||
* @return a DBusReturn object containing the error
|
||||
*/
|
||||
static DBusReturn Error(QDBusError::ErrorType errorType)
|
||||
{
|
||||
return DBusReturn{QDBusError::errorString(errorType), DBusReturnImpl::WithErrorTag{}};
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded version
|
||||
* @param errorName
|
||||
* @return a DBusReturnImpl object containing the error
|
||||
*/
|
||||
static DBusReturn Error(QString errorName)
|
||||
{
|
||||
return DBusReturn{std::move(errorName), DBusReturnImpl::WithErrorTag{}};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reference to the enclosed value
|
||||
* @return
|
||||
*/
|
||||
const T& value() const&
|
||||
{
|
||||
okOrDie();
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a rvalue reference to the enclosed value if this object is rvalue
|
||||
* @return a rvalue reference to the enclosed value
|
||||
*/
|
||||
T value() &&
|
||||
{
|
||||
okOrDie();
|
||||
return std::move(m_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value or handle the error by the passed in dbus object
|
||||
* @tparam P
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
template <typename P> T valueOrHandle(P* p) const&
|
||||
{
|
||||
if (isError()) {
|
||||
if (p->calledFromDBus()) {
|
||||
p->sendErrorReply(errorName());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value or handle the error by the passed in dbus object
|
||||
* @tparam P
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
template <typename P> T&& valueOrHandle(P* p) &&
|
||||
{
|
||||
if (isError()) {
|
||||
if (p->calledFromDBus()) {
|
||||
p->sendErrorReply(errorName());
|
||||
}
|
||||
}
|
||||
return std::move(m_value);
|
||||
}
|
||||
|
||||
private:
|
||||
T m_value{};
|
||||
};
|
||||
|
||||
template <> class DBusReturn<void> : public details::DBusReturnImpl
|
||||
{
|
||||
protected:
|
||||
using DBusReturnImpl::DBusReturnImpl;
|
||||
|
||||
public:
|
||||
using value_type = void;
|
||||
|
||||
DBusReturn() = default;
|
||||
|
||||
/**
|
||||
* Implicitly convert from another error of different value type.
|
||||
*
|
||||
* @tparam U must not be the same as T
|
||||
* @param other
|
||||
*/
|
||||
template <typename U, typename = typename std::enable_if<!std::is_same<void, U>::value>::type>
|
||||
DBusReturn(const DBusReturn<U>& other) // NOLINT(google-explicit-constructor)
|
||||
: DBusReturn(other.errorName(), DBusReturnImpl::WithErrorTag{})
|
||||
{
|
||||
Q_ASSERT(other.isError());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from error
|
||||
* @param errorType
|
||||
* @return a DBusReturn object containing the error
|
||||
*/
|
||||
static DBusReturn Error(QDBusError::ErrorType errorType)
|
||||
{
|
||||
return DBusReturn{QDBusError::errorString(errorType), DBusReturnImpl::WithErrorTag{}};
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded version
|
||||
* @param errorName
|
||||
* @return a DBusReturnImpl object containing the error
|
||||
*/
|
||||
static DBusReturn Error(QString errorName)
|
||||
{
|
||||
return DBusReturn{std::move(errorName), DBusReturnImpl::WithErrorTag{}};
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is return contains an error, handle it if we were called from DBus
|
||||
* @tparam P
|
||||
* @param p
|
||||
*/
|
||||
template <typename P> void handle(P* p) const
|
||||
{
|
||||
if (isError()) {
|
||||
if (p->calledFromDBus()) {
|
||||
p->sendErrorReply(errorName());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSRETURN_H
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
* Copyright 2010, Michael Leupold <lemma@confuego.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 or (at your option)
|
||||
* 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 "DBusTypes.h"
|
||||
|
||||
#include <QDBusMetaType>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
void registerDBusTypes()
|
||||
{
|
||||
// register meta-types needed for this adaptor
|
||||
qRegisterMetaType<SecretStruct>();
|
||||
qDBusRegisterMetaType<SecretStruct>();
|
||||
|
||||
qRegisterMetaType<StringStringMap>();
|
||||
qDBusRegisterMetaType<StringStringMap>();
|
||||
|
||||
qRegisterMetaType<ObjectPathSecretMap>();
|
||||
qDBusRegisterMetaType<ObjectPathSecretMap>();
|
||||
|
||||
QMetaType::registerConverter<QDBusArgument, StringStringMap>([](const QDBusArgument& arg) {
|
||||
if (arg.currentSignature() != "a{ss}") {
|
||||
return StringStringMap{};
|
||||
}
|
||||
// QDBusArgument is COW and qdbus_cast modifies it by detaching even it is const.
|
||||
// we don't want to modify the instance (arg) stored in the qvariant so we create a copy
|
||||
const auto copy = arg; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
return qdbus_cast<StringStringMap>(copy);
|
||||
});
|
||||
|
||||
// NOTE: this is already registered by Qt in qtextratypes.h
|
||||
// qRegisterMetaType<QList<QDBusObjectPath > >();
|
||||
// qDBusRegisterMetaType<QList<QDBusObjectPath> >();
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
* Copyright 2010, Michael Leupold <lemma@confuego.org>
|
||||
* Copyright 2010-2011, Valentin Rusu <valir@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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSTYPES_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSTYPES_H
|
||||
|
||||
#include <QDBusArgument>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#define DBUS_SERVICE_SECRET "org.freedesktop.secrets"
|
||||
|
||||
#define DBUS_INTERFACE_SECRET_SERVICE "org.freedesktop.Secret.Service"
|
||||
#define DBUS_INTERFACE_SECRET_SESSION "org.freedesktop.Secret.Session"
|
||||
#define DBUS_INTERFACE_SECRET_COLLECTION "org.freedesktop.Secret.Collection"
|
||||
#define DBUS_INTERFACE_SECRET_ITEM "org.freedesktop.Secret.Item"
|
||||
#define DBUS_INTERFACE_SECRET_PROMPT "org.freedesktop.Secret.Prompt"
|
||||
|
||||
#define DBUS_ERROR_SECRET_NO_SESSION "org.freedesktop.Secret.Error.NoSession"
|
||||
#define DBUS_ERROR_SECRET_NO_SUCH_OBJECT "org.freedesktop.Secret.Error.NoSuchObject"
|
||||
#define DBUS_ERROR_SECRET_IS_LOCKED "org.freedesktop.Secret.Error.IsLocked"
|
||||
|
||||
#define DBUS_PATH_SECRETS "/org/freedesktop/secrets"
|
||||
|
||||
#define DBUS_PATH_TEMPLATE_ALIAS "%1/aliases/%2"
|
||||
#define DBUS_PATH_TEMPLATE_SESSION "%1/session/%2"
|
||||
#define DBUS_PATH_TEMPLATE_COLLECTION "%1/collection/%2"
|
||||
#define DBUS_PATH_TEMPLATE_ITEM "%1/%2"
|
||||
#define DBUS_PATH_TEMPLATE_PROMPT "%1/prompt/%2"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
/**
|
||||
* This is the basic Secret structure exchanged via the dbus API
|
||||
* See the spec for more details
|
||||
*/
|
||||
struct SecretStruct
|
||||
{
|
||||
QDBusObjectPath session{};
|
||||
QByteArray parameters{};
|
||||
QByteArray value{};
|
||||
QString contentType{};
|
||||
};
|
||||
|
||||
inline QDBusArgument& operator<<(QDBusArgument& argument, const SecretStruct& secret)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << secret.session << secret.parameters << secret.value << secret.contentType;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline const QDBusArgument& operator>>(const QDBusArgument& argument, SecretStruct& secret)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> secret.session >> secret.parameters >> secret.value >> secret.contentType;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the types needed for the fd.o Secrets D-Bus interface.
|
||||
*/
|
||||
void registerDBusTypes();
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
typedef QMap<QString, QString> StringStringMap;
|
||||
typedef QMap<QDBusObjectPath, FdoSecrets::SecretStruct> ObjectPathSecretMap;
|
||||
|
||||
Q_DECLARE_METATYPE(FdoSecrets::SecretStruct)
|
||||
Q_DECLARE_METATYPE(StringStringMap);
|
||||
Q_DECLARE_METATYPE(ObjectPathSecretMap);
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSTYPES_H
|
@ -18,6 +18,7 @@
|
||||
#include "Item.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
@ -40,7 +41,7 @@ namespace FdoSecrets
|
||||
const QSet<QString> Item::ReadOnlyAttributes(QSet<QString>() << ItemAttributes::UuidKey << ItemAttributes::PathKey);
|
||||
|
||||
static void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType);
|
||||
static SecretStruct getEntrySecret(Entry* entry);
|
||||
static Secret getEntrySecret(Entry* entry);
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -51,8 +52,7 @@ namespace FdoSecrets
|
||||
Item* Item::Create(Collection* parent, Entry* backend)
|
||||
{
|
||||
QScopedPointer<Item> res{new Item(parent, backend)};
|
||||
|
||||
if (!res->registerSelf()) {
|
||||
if (!res->dbus()->registerObject(res.data())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -60,46 +60,37 @@ namespace FdoSecrets
|
||||
}
|
||||
|
||||
Item::Item(Collection* parent, Entry* backend)
|
||||
: DBusObjectHelper(parent)
|
||||
: DBusObject(parent)
|
||||
, m_backend(backend)
|
||||
{
|
||||
Q_ASSERT(!p()->objectPath().path().isEmpty());
|
||||
|
||||
connect(m_backend.data(), &Entry::entryModified, this, &Item::itemChanged);
|
||||
}
|
||||
|
||||
bool Item::registerSelf()
|
||||
{
|
||||
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_ITEM).arg(p()->objectPath().path(), m_backend->uuidToHex());
|
||||
bool ok = registerWithPath(path);
|
||||
if (!ok) {
|
||||
service()->plugin()->emitError(tr("Failed to register item on DBus at path '%1'").arg(path));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
DBusReturn<bool> Item::locked() const
|
||||
DBusResult Item::locked(const DBusClientPtr& client, bool& locked) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
return collection()->locked();
|
||||
ret = collection()->locked(locked);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
locked = locked || !client->itemAuthorized(m_backend->uuid());
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const StringStringMap> Item::attributes() const
|
||||
DBusResult Item::attributes(StringStringMap& attrs) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
StringStringMap attrs;
|
||||
|
||||
// add default attributes except password
|
||||
auto entryAttrs = m_backend->attributes();
|
||||
for (const auto& attr : EntryAttributes::DefaultAttributes) {
|
||||
@ -124,17 +115,17 @@ namespace FdoSecrets
|
||||
// add some informative and readonly attributes
|
||||
attrs[ItemAttributes::UuidKey] = m_backend->uuidToHex();
|
||||
attrs[ItemAttributes::PathKey] = path();
|
||||
return attrs;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setAttributes(const StringStringMap& attrs)
|
||||
DBusResult Item::setAttributes(const StringStringMap& attrs)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -158,28 +149,29 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<QString> Item::label() const
|
||||
DBusResult Item::label(QString& label) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return m_backend->title();
|
||||
label = m_backend->title();
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setLabel(const QString& label)
|
||||
DBusResult Item::setLabel(const QString& label)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -190,91 +182,106 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Item::created() const
|
||||
DBusResult Item::created(qulonglong& created) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return static_cast<qulonglong>(m_backend->timeInfo().creationTime().toMSecsSinceEpoch() / 1000);
|
||||
created = static_cast<qulonglong>(m_backend->timeInfo().creationTime().toMSecsSinceEpoch() / 1000);
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Item::modified() const
|
||||
DBusResult Item::modified(qulonglong& modified) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return static_cast<qulonglong>(m_backend->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000);
|
||||
modified = static_cast<qulonglong>(m_backend->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000);
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<PromptBase*> Item::deleteItem()
|
||||
DBusResult Item::remove(PromptBase*& prompt)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
auto prompt = DeleteItemPrompt::Create(service(), this);
|
||||
return prompt.value();
|
||||
prompt = PromptBase::Create<DeleteItemPrompt>(service(), this);
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<SecretStruct> Item::getSecret(Session* session)
|
||||
DBusResult Item::getSecret(const DBusClientPtr& client, Session* session, Secret& secret)
|
||||
{
|
||||
auto ret = getSecretNoNotification(client, session, secret);
|
||||
if (ret.ok()) {
|
||||
service()->plugin()->emitRequestShowNotification(
|
||||
tr(R"(Entry "%1" from database "%2" was used by %3)")
|
||||
.arg(m_backend->title(), collection()->name(), client->name()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
DBusResult Item::getSecretNoNotification(const DBusClientPtr& client, Session* session, Secret& secret) const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (!client->itemAuthorizedResetOnce(backend()->uuid())) {
|
||||
return DBusResult(DBUS_ERROR_SECRET_IS_LOCKED);
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SESSION);
|
||||
}
|
||||
|
||||
auto secret = getEntrySecret(m_backend);
|
||||
secret = getEntrySecret(m_backend);
|
||||
|
||||
// encode using session
|
||||
secret = session->encode(secret);
|
||||
|
||||
// show notification is this was directly called from DBus
|
||||
if (calledFromDBus()) {
|
||||
service()->plugin()->emitRequestShowNotification(
|
||||
tr(R"(Entry "%1" from database "%2" was used by %3)")
|
||||
.arg(m_backend->title(), collection()->name(), callingPeerName()));
|
||||
}
|
||||
return secret;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setSecret(const SecretStruct& secret)
|
||||
DBusResult Item::setSecret(const DBusClientPtr& client, const Secret& secret)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (!client->itemAuthorizedResetOnce(backend()->uuid())) {
|
||||
return DBusResult(DBUS_ERROR_SECRET_IS_LOCKED);
|
||||
}
|
||||
|
||||
auto session = pathToObject<Session>(secret.session);
|
||||
if (!session) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
if (!secret.session) {
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SESSION);
|
||||
}
|
||||
|
||||
// decode using session
|
||||
auto decoded = session->decode(secret);
|
||||
auto decoded = secret.session->decode(secret);
|
||||
|
||||
// set in backend
|
||||
m_backend->beginUpdate();
|
||||
@ -284,19 +291,18 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setProperties(const QVariantMap& properties)
|
||||
DBusResult Item::setProperties(const QVariantMap& properties)
|
||||
{
|
||||
auto label = properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Label")).toString();
|
||||
auto label = properties.value(DBUS_INTERFACE_SECRET_ITEM + ".Label").toString();
|
||||
|
||||
auto ret = setLabel(label);
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto attributes =
|
||||
properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes")).value<StringStringMap>();
|
||||
auto attributes = properties.value(DBUS_INTERFACE_SECRET_ITEM + ".Attributes").value<StringStringMap>();
|
||||
ret = setAttributes(attributes);
|
||||
if (ret.isError()) {
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -305,25 +311,26 @@ namespace FdoSecrets
|
||||
|
||||
Collection* Item::collection() const
|
||||
{
|
||||
return qobject_cast<Collection*>(p());
|
||||
return qobject_cast<Collection*>(parent());
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::ensureBackend() const
|
||||
DBusResult Item::ensureBackend() const
|
||||
{
|
||||
if (!m_backend) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::ensureUnlocked() const
|
||||
DBusResult Item::ensureUnlocked() const
|
||||
{
|
||||
auto locked = collection()->locked();
|
||||
if (locked.isError()) {
|
||||
return locked;
|
||||
bool l;
|
||||
auto ret = collection()->locked(l);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (locked.value()) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_IS_LOCKED));
|
||||
if (l) {
|
||||
return DBusResult(DBUS_ERROR_SECRET_IS_LOCKED);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@ -340,7 +347,7 @@ namespace FdoSecrets
|
||||
// Unregister current path early, do not rely on deleteLater's call to destructor
|
||||
// as in case of Entry moving between groups, new Item will be created at the same DBus path
|
||||
// before the current Item is deleted in the event loop.
|
||||
unregisterPrimaryPath();
|
||||
dbus()->unregisterObject(this);
|
||||
|
||||
m_backend = nullptr;
|
||||
deleteLater();
|
||||
@ -369,13 +376,6 @@ namespace FdoSecrets
|
||||
return pathComponents.join('/');
|
||||
}
|
||||
|
||||
bool Item::isDeletePermanent() const
|
||||
{
|
||||
auto recycleBin = backend()->database()->metadata()->recycleBin();
|
||||
return (recycleBin && recycleBin->findEntryByUuid(backend()->uuid()))
|
||||
|| !backend()->database()->metadata()->recycleBinEnabled();
|
||||
}
|
||||
|
||||
void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType)
|
||||
{
|
||||
auto mimeName = contentType.split(';').takeFirst().trimmed();
|
||||
@ -414,9 +414,9 @@ namespace FdoSecrets
|
||||
entry->setPassword(codec->toUnicode(data));
|
||||
}
|
||||
|
||||
SecretStruct getEntrySecret(Entry* entry)
|
||||
Secret getEntrySecret(Entry* entry)
|
||||
{
|
||||
SecretStruct ss;
|
||||
Secret ss{};
|
||||
|
||||
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
||||
ss.value = entry->attachments()->value(FDO_SECRETS_DATA);
|
||||
|
@ -18,8 +18,8 @@
|
||||
#ifndef KEEPASSXC_FDOSECRETS_ITEM_H
|
||||
#define KEEPASSXC_FDOSECRETS_ITEM_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/adaptors/ItemAdaptor.h"
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
@ -38,9 +38,10 @@ namespace FdoSecrets
|
||||
class Collection;
|
||||
class PromptBase;
|
||||
|
||||
class Item : public DBusObjectHelper<Item, ItemAdaptor>
|
||||
class Item : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_ITEM_LITERAL)
|
||||
|
||||
explicit Item(Collection* parent, Entry* backend);
|
||||
|
||||
@ -55,21 +56,21 @@ namespace FdoSecrets
|
||||
*/
|
||||
static Item* Create(Collection* parent, Entry* backend);
|
||||
|
||||
DBusReturn<bool> locked() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult locked(const DBusClientPtr& client, bool& locked) const;
|
||||
|
||||
DBusReturn<const StringStringMap> attributes() const;
|
||||
DBusReturn<void> setAttributes(const StringStringMap& attrs);
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult attributes(StringStringMap& attrs) const;
|
||||
Q_INVOKABLE DBusResult setAttributes(const StringStringMap& attrs);
|
||||
|
||||
DBusReturn<QString> label() const;
|
||||
DBusReturn<void> setLabel(const QString& label);
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult label(QString& label) const;
|
||||
Q_INVOKABLE DBusResult setLabel(const QString& label);
|
||||
|
||||
DBusReturn<qulonglong> created() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult created(qulonglong& created) const;
|
||||
|
||||
DBusReturn<qulonglong> modified() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult modified(qulonglong& modified) const;
|
||||
|
||||
DBusReturn<PromptBase*> deleteItem();
|
||||
DBusReturn<SecretStruct> getSecret(Session* session);
|
||||
DBusReturn<void> setSecret(const SecretStruct& secret);
|
||||
Q_INVOKABLE DBusResult remove(PromptBase*& prompt);
|
||||
Q_INVOKABLE DBusResult getSecret(const DBusClientPtr& client, Session* session, Secret& secret);
|
||||
Q_INVOKABLE DBusResult setSecret(const DBusClientPtr& client, const Secret& secret);
|
||||
|
||||
signals:
|
||||
void itemChanged();
|
||||
@ -78,7 +79,8 @@ namespace FdoSecrets
|
||||
public:
|
||||
static const QSet<QString> ReadOnlyAttributes;
|
||||
|
||||
DBusReturn<void> setProperties(const QVariantMap& properties);
|
||||
DBusResult getSecretNoNotification(const DBusClientPtr& client, Session* session, Secret& secret) const;
|
||||
DBusResult setProperties(const QVariantMap& properties);
|
||||
|
||||
Entry* backend() const;
|
||||
Collection* collection() const;
|
||||
@ -90,39 +92,26 @@ namespace FdoSecrets
|
||||
*/
|
||||
QString path() const;
|
||||
|
||||
/**
|
||||
* If the containing db does not have recycle bin enabled,
|
||||
* or the entry is already in the recycle bin (not possible for item, though),
|
||||
* the delete is permanent
|
||||
* @return true if delete is permanent
|
||||
*/
|
||||
bool isDeletePermanent() const;
|
||||
|
||||
public slots:
|
||||
void doDelete();
|
||||
|
||||
/**
|
||||
* @brief Register self on DBus
|
||||
* @return
|
||||
*/
|
||||
bool registerSelf();
|
||||
|
||||
/**
|
||||
* Check if the backend is a valid object, send error reply if not.
|
||||
* @return No error if the backend is valid.
|
||||
*/
|
||||
DBusReturn<void> ensureBackend() const;
|
||||
DBusResult ensureBackend() const;
|
||||
|
||||
/**
|
||||
* Ensure the database is unlocked, send error reply if locked.
|
||||
* @return true if the database is locked
|
||||
*/
|
||||
DBusReturn<void> ensureUnlocked() const;
|
||||
DBusResult ensureUnlocked() const;
|
||||
|
||||
private:
|
||||
QPointer<Entry> m_backend;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
Q_DECLARE_METATYPE(FdoSecrets::ItemSecretMap);
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_ITEM_H
|
||||
|
@ -18,10 +18,12 @@
|
||||
#include "Prompt.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
#include "fdosecrets/widgets/AccessControlDialog.h"
|
||||
|
||||
#include "core/Tools.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
@ -29,27 +31,17 @@
|
||||
|
||||
#include <QThread>
|
||||
#include <QWindow>
|
||||
#include <utility>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
PromptBase::PromptBase(Service* parent)
|
||||
: DBusObjectHelper(parent)
|
||||
: DBusObject(parent)
|
||||
{
|
||||
connect(this, &PromptBase::completed, this, &PromptBase::deleteLater);
|
||||
}
|
||||
|
||||
bool PromptBase::registerSelf()
|
||||
{
|
||||
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_PROMPT)
|
||||
.arg(p()->objectPath().path(), Tools::uuidToHex(QUuid::createUuid()));
|
||||
bool ok = registerWithPath(path);
|
||||
if (!ok) {
|
||||
service()->plugin()->emitError(tr("Failed to register item on DBus at path '%1'").arg(path));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
QWindow* PromptBase::findWindow(const QString& windowId)
|
||||
{
|
||||
// find parent window, or nullptr if not found
|
||||
@ -71,41 +63,29 @@ namespace FdoSecrets
|
||||
return qobject_cast<Service*>(parent());
|
||||
}
|
||||
|
||||
DBusReturn<void> PromptBase::dismiss()
|
||||
DBusResult PromptBase::dismiss()
|
||||
{
|
||||
emit completed(true, "");
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<DeleteCollectionPrompt*> DeleteCollectionPrompt::Create(Service* parent, Collection* coll)
|
||||
{
|
||||
QScopedPointer<DeleteCollectionPrompt> res{new DeleteCollectionPrompt(parent, coll)};
|
||||
if (!res->registerSelf()) {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
}
|
||||
return res.take();
|
||||
}
|
||||
|
||||
DeleteCollectionPrompt::DeleteCollectionPrompt(Service* parent, Collection* coll)
|
||||
: PromptBase(parent)
|
||||
, m_collection(coll)
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn<void> DeleteCollectionPrompt::prompt(const QString& windowId)
|
||||
DBusResult DeleteCollectionPrompt::prompt(const DBusClientPtr&, const QString& windowId)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
DBusReturn<void> ret;
|
||||
QMetaObject::invokeMethod(this,
|
||||
"prompt",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, windowId),
|
||||
Q_RETURN_ARG(DBusReturn<void>, ret));
|
||||
DBusResult ret;
|
||||
QMetaObject::invokeMethod(
|
||||
this, "prompt", Qt::BlockingQueuedConnection, Q_ARG(QString, windowId), Q_RETURN_ARG(DBusResult, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!m_collection) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT);
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
@ -117,29 +97,19 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<CreateCollectionPrompt*> CreateCollectionPrompt::Create(Service* parent)
|
||||
{
|
||||
QScopedPointer<CreateCollectionPrompt> res{new CreateCollectionPrompt(parent)};
|
||||
if (!res->registerSelf()) {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
}
|
||||
return res.take();
|
||||
}
|
||||
|
||||
CreateCollectionPrompt::CreateCollectionPrompt(Service* parent)
|
||||
CreateCollectionPrompt::CreateCollectionPrompt(Service* parent, QVariantMap properties, QString alias)
|
||||
: PromptBase(parent)
|
||||
, m_properties(std::move(properties))
|
||||
, m_alias(std::move(alias))
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn<void> CreateCollectionPrompt::prompt(const QString& windowId)
|
||||
DBusResult CreateCollectionPrompt::prompt(const DBusClientPtr&, const QString& windowId)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
DBusReturn<void> ret;
|
||||
QMetaObject::invokeMethod(this,
|
||||
"prompt",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, windowId),
|
||||
Q_RETURN_ARG(DBusReturn<void>, ret));
|
||||
DBusResult ret;
|
||||
QMetaObject::invokeMethod(
|
||||
this, "prompt", Qt::BlockingQueuedConnection, Q_ARG(QString, windowId), Q_RETURN_ARG(DBusResult, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -150,27 +120,30 @@ namespace FdoSecrets
|
||||
return dismiss();
|
||||
}
|
||||
|
||||
emit collectionCreated(coll);
|
||||
auto ret = coll->setProperties(m_properties);
|
||||
if (ret.err()) {
|
||||
coll->doDelete();
|
||||
return dismiss();
|
||||
}
|
||||
if (!m_alias.isEmpty()) {
|
||||
ret = coll->addAlias(m_alias);
|
||||
if (ret.err()) {
|
||||
coll->doDelete();
|
||||
return dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
emit completed(false, QVariant::fromValue(coll->objectPath()));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> CreateCollectionPrompt::dismiss()
|
||||
DBusResult CreateCollectionPrompt::dismiss()
|
||||
{
|
||||
emit completed(true, QVariant::fromValue(QDBusObjectPath{"/"}));
|
||||
emit completed(true, QVariant::fromValue(DBusMgr::objectPathSafe(nullptr)));
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<LockCollectionsPrompt*> LockCollectionsPrompt::Create(Service* parent, const QList<Collection*>& colls)
|
||||
{
|
||||
QScopedPointer<LockCollectionsPrompt> res{new LockCollectionsPrompt(parent, colls)};
|
||||
if (!res->registerSelf()) {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
}
|
||||
return res.take();
|
||||
}
|
||||
|
||||
LockCollectionsPrompt::LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
||||
: PromptBase(parent)
|
||||
{
|
||||
@ -180,15 +153,12 @@ namespace FdoSecrets
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<void> LockCollectionsPrompt::prompt(const QString& windowId)
|
||||
DBusResult LockCollectionsPrompt::prompt(const DBusClientPtr&, const QString& windowId)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
DBusReturn<void> ret;
|
||||
QMetaObject::invokeMethod(this,
|
||||
"prompt",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, windowId),
|
||||
Q_RETURN_ARG(DBusReturn<void>, ret));
|
||||
DBusResult ret;
|
||||
QMetaObject::invokeMethod(
|
||||
this, "prompt", Qt::BlockingQueuedConnection, Q_ARG(QString, windowId), Q_RETURN_ARG(DBusResult, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -208,113 +178,177 @@ namespace FdoSecrets
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> LockCollectionsPrompt::dismiss()
|
||||
DBusResult LockCollectionsPrompt::dismiss()
|
||||
{
|
||||
emit completed(true, QVariant::fromValue(m_locked));
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<UnlockCollectionsPrompt*> UnlockCollectionsPrompt::Create(Service* parent,
|
||||
const QList<Collection*>& coll)
|
||||
{
|
||||
QScopedPointer<UnlockCollectionsPrompt> res{new UnlockCollectionsPrompt(parent, coll)};
|
||||
if (!res->registerSelf()) {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
}
|
||||
return res.take();
|
||||
}
|
||||
|
||||
UnlockCollectionsPrompt::UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
||||
UnlockPrompt::UnlockPrompt(Service* parent, const QSet<Collection*>& colls, const QSet<Item*>& items)
|
||||
: PromptBase(parent)
|
||||
{
|
||||
m_collections.reserve(colls.size());
|
||||
for (const auto& c : asConst(colls)) {
|
||||
m_collections << c;
|
||||
for (const auto& coll : asConst(colls)) {
|
||||
m_collections << coll;
|
||||
connect(coll, &Collection::doneUnlockCollection, this, &UnlockPrompt::collectionUnlockFinished);
|
||||
}
|
||||
for (const auto& item : asConst(items)) {
|
||||
m_items[item->collection()] << item;
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<void> UnlockCollectionsPrompt::prompt(const QString& windowId)
|
||||
DBusResult UnlockPrompt::prompt(const DBusClientPtr& client, const QString& windowId)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
DBusReturn<void> ret;
|
||||
QMetaObject::invokeMethod(this,
|
||||
"prompt",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, windowId),
|
||||
Q_RETURN_ARG(DBusReturn<void>, ret));
|
||||
DBusResult ret;
|
||||
QMetaObject::invokeMethod(
|
||||
this, "prompt", Qt::BlockingQueuedConnection, Q_ARG(QString, windowId), Q_RETURN_ARG(DBusResult, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
|
||||
// for use in unlockItems
|
||||
m_windowId = windowId;
|
||||
m_client = client;
|
||||
|
||||
// first unlock any collections
|
||||
bool waitingForCollections = false;
|
||||
for (const auto& c : asConst(m_collections)) {
|
||||
if (c) {
|
||||
// doUnlock is nonblocking
|
||||
connect(c, &Collection::doneUnlockCollection, this, &UnlockCollectionsPrompt::collectionUnlockFinished);
|
||||
// doUnlock is nonblocking, execution will continue in collectionUnlockFinished
|
||||
c->doUnlock();
|
||||
waitingForCollections = true;
|
||||
}
|
||||
}
|
||||
|
||||
// unlock items directly if no collection unlocking pending
|
||||
// o.w. do it in collectionUnlockFinished
|
||||
if (!waitingForCollections) {
|
||||
// do not block the current method
|
||||
QTimer::singleShot(0, this, &UnlockPrompt::unlockItems);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void UnlockCollectionsPrompt::collectionUnlockFinished(bool accepted)
|
||||
void UnlockPrompt::collectionUnlockFinished(bool accepted)
|
||||
{
|
||||
auto coll = qobject_cast<Collection*>(sender());
|
||||
if (!coll) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_collections.contains(coll)) {
|
||||
// should not happen
|
||||
coll->disconnect(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// one shot
|
||||
coll->disconnect(this);
|
||||
|
||||
if (!m_collections.contains(coll)) {
|
||||
// should not happen
|
||||
return;
|
||||
}
|
||||
|
||||
if (accepted) {
|
||||
m_unlocked << coll->objectPath();
|
||||
} else {
|
||||
m_numRejected += 1;
|
||||
// no longer need to unlock the item if its containing collection
|
||||
// didn't unlock.
|
||||
m_items.remove(coll);
|
||||
}
|
||||
|
||||
// if we've get all
|
||||
// if we got response for all collections
|
||||
if (m_numRejected + m_unlocked.size() == m_collections.size()) {
|
||||
emit completed(m_unlocked.isEmpty(), QVariant::fromValue(m_unlocked));
|
||||
// next step is to unlock items
|
||||
unlockItems();
|
||||
}
|
||||
}
|
||||
DBusReturn<void> UnlockCollectionsPrompt::dismiss()
|
||||
|
||||
void UnlockPrompt::unlockItems()
|
||||
{
|
||||
auto client = m_client.lock();
|
||||
if (!client) {
|
||||
// client already gone
|
||||
return;
|
||||
}
|
||||
|
||||
// flatten to list of entries
|
||||
QList<Entry*> entries;
|
||||
for (const auto& itemsPerColl : m_items.values()) {
|
||||
for (const auto& item : itemsPerColl) {
|
||||
if (!item) {
|
||||
m_numRejected += 1;
|
||||
continue;
|
||||
}
|
||||
auto entry = item->backend();
|
||||
if (client->itemKnown(entry->uuid())) {
|
||||
if (!client->itemAuthorized(entry->uuid())) {
|
||||
m_numRejected += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// attach a temporary property so later we can get the item
|
||||
// back from the dialog's result
|
||||
entry->setProperty(FdoSecretsBackend, QVariant::fromValue(item.data()));
|
||||
entries << entry;
|
||||
}
|
||||
}
|
||||
if (!entries.isEmpty()) {
|
||||
QString app = tr("%1 (PID: %2)").arg(client->name()).arg(client->pid());
|
||||
auto ac = new AccessControlDialog(findWindow(m_windowId), entries, app, AuthOption::Remember);
|
||||
connect(ac, &AccessControlDialog::finished, this, &UnlockPrompt::itemUnlockFinished);
|
||||
connect(ac, &AccessControlDialog::finished, ac, &AccessControlDialog::deleteLater);
|
||||
ac->open();
|
||||
} else {
|
||||
itemUnlockFinished({});
|
||||
}
|
||||
}
|
||||
|
||||
void UnlockPrompt::itemUnlockFinished(const QHash<Entry*, AuthDecision>& decisions)
|
||||
{
|
||||
auto client = m_client.lock();
|
||||
if (!client) {
|
||||
// client already gone
|
||||
return;
|
||||
}
|
||||
for (auto it = decisions.constBegin(); it != decisions.constEnd(); ++it) {
|
||||
auto entry = it.key();
|
||||
// get back the corresponding item
|
||||
auto item = entry->property(FdoSecretsBackend).value<Item*>();
|
||||
entry->setProperty(FdoSecretsBackend, {});
|
||||
Q_ASSERT(item);
|
||||
|
||||
// set auth
|
||||
client->setItemAuthorized(entry->uuid(), it.value());
|
||||
|
||||
if (client->itemAuthorized(entry->uuid())) {
|
||||
m_unlocked += item->objectPath();
|
||||
} else {
|
||||
m_numRejected += 1;
|
||||
}
|
||||
}
|
||||
// if anything is not unlocked, treat the whole prompt as dismissed
|
||||
// so the client has a chance to handle the error
|
||||
emit completed(m_numRejected > 0, QVariant::fromValue(m_unlocked));
|
||||
}
|
||||
|
||||
DBusResult UnlockPrompt::dismiss()
|
||||
{
|
||||
emit completed(true, QVariant::fromValue(m_unlocked));
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<DeleteItemPrompt*> DeleteItemPrompt::Create(Service* parent, Item* item)
|
||||
{
|
||||
QScopedPointer<DeleteItemPrompt> res{new DeleteItemPrompt(parent, item)};
|
||||
if (!res->registerSelf()) {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
}
|
||||
return res.take();
|
||||
}
|
||||
|
||||
DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item)
|
||||
: PromptBase(parent)
|
||||
, m_item(item)
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn<void> DeleteItemPrompt::prompt(const QString& windowId)
|
||||
DBusResult DeleteItemPrompt::prompt(const DBusClientPtr&, const QString& windowId)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
DBusReturn<void> ret;
|
||||
QMetaObject::invokeMethod(this,
|
||||
"prompt",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, windowId),
|
||||
Q_RETURN_ARG(DBusReturn<void>, ret));
|
||||
DBusResult ret;
|
||||
QMetaObject::invokeMethod(
|
||||
this, "prompt", Qt::BlockingQueuedConnection, Q_ARG(QString, windowId), Q_RETURN_ARG(DBusResult, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -322,14 +356,6 @@ namespace FdoSecrets
|
||||
|
||||
// delete item's backend. Item will be notified after the backend is deleted.
|
||||
if (m_item) {
|
||||
if (FdoSecrets::settings()->noConfirmDeleteItem()) {
|
||||
// based on permanent or not, different button is used
|
||||
if (m_item->isDeletePermanent()) {
|
||||
MessageBox::setNextAnswer(MessageBox::Delete);
|
||||
} else {
|
||||
MessageBox::setNextAnswer(MessageBox::Move);
|
||||
}
|
||||
}
|
||||
m_item->collection()->doDeleteEntries({m_item->backend()});
|
||||
}
|
||||
|
||||
@ -337,4 +363,121 @@ namespace FdoSecrets
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CreateItemPrompt::CreateItemPrompt(Service* parent,
|
||||
Collection* coll,
|
||||
QVariantMap properties,
|
||||
Secret secret,
|
||||
QString itemPath,
|
||||
Item* existing)
|
||||
: PromptBase(parent)
|
||||
, m_coll(coll)
|
||||
, m_properties(std::move(properties))
|
||||
, m_secret(std::move(secret))
|
||||
, m_itemPath(std::move(itemPath))
|
||||
, m_item(existing)
|
||||
// session aliveness also need to be tracked, for potential use later in updateItem
|
||||
, m_sess(m_secret.session)
|
||||
{
|
||||
}
|
||||
|
||||
DBusResult CreateItemPrompt::prompt(const DBusClientPtr& client, const QString& windowId)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
DBusResult ret;
|
||||
QMetaObject::invokeMethod(
|
||||
this, "prompt", Qt::BlockingQueuedConnection, Q_ARG(QString, windowId), Q_RETURN_ARG(DBusResult, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
|
||||
if (!m_coll) {
|
||||
return dismiss();
|
||||
}
|
||||
|
||||
// save a weak reference to the client which may be used asynchronously later
|
||||
m_client = client;
|
||||
|
||||
// the item doesn't exists yet, create it
|
||||
if (!m_item) {
|
||||
m_item = m_coll->doNewItem(client, m_itemPath);
|
||||
if (!m_item) {
|
||||
// may happen if entry somehow ends up in recycle bin
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT);
|
||||
}
|
||||
|
||||
auto ret = updateItem();
|
||||
if (ret.err()) {
|
||||
m_item->doDelete();
|
||||
return ret;
|
||||
}
|
||||
emit completed(false, QVariant::fromValue(m_item->objectPath()));
|
||||
} else {
|
||||
bool locked = false;
|
||||
auto ret = m_item->locked(client, locked);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (locked) {
|
||||
// give the user a chance to unlock the item
|
||||
auto prompt = PromptBase::Create<UnlockPrompt>(service(), QSet<Collection*>{}, QSet<Item*>{m_item});
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
// postpone anything after the confirmation
|
||||
connect(prompt, &PromptBase::completed, this, &CreateItemPrompt::itemUnlocked);
|
||||
return prompt->prompt(client, windowId);
|
||||
} else {
|
||||
ret = updateItem();
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
emit completed(false, QVariant::fromValue(m_item->objectPath()));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusResult CreateItemPrompt::dismiss()
|
||||
{
|
||||
emit completed(true, QVariant::fromValue(DBusMgr::objectPathSafe(nullptr)));
|
||||
return {};
|
||||
}
|
||||
|
||||
void CreateItemPrompt::itemUnlocked(bool dismissed, const QVariant& result)
|
||||
{
|
||||
auto unlocked = result.value<QList<QDBusObjectPath>>();
|
||||
if (!unlocked.isEmpty()) {
|
||||
// in theory we should check if the object path matches m_item, but a mismatch should not happen,
|
||||
// because we control the unlock prompt ourselves
|
||||
updateItem();
|
||||
}
|
||||
emit completed(dismissed, QVariant::fromValue(DBusMgr::objectPathSafe(m_item)));
|
||||
}
|
||||
|
||||
DBusResult CreateItemPrompt::updateItem()
|
||||
{
|
||||
auto client = m_client.lock();
|
||||
if (!client) {
|
||||
// client already gone
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!m_sess || m_sess != m_secret.session) {
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SESSION);
|
||||
}
|
||||
if (!m_item) {
|
||||
return {};
|
||||
}
|
||||
auto ret = m_item->setProperties(m_properties);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
ret = m_item->setSecret(client, m_secret);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace FdoSecrets
|
||||
|
@ -18,27 +18,41 @@
|
||||
#ifndef KEEPASSXC_FDOSECRETS_PROMPT_H
|
||||
#define KEEPASSXC_FDOSECRETS_PROMPT_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/adaptors/PromptAdaptor.h"
|
||||
#include "core/Global.h"
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
|
||||
class QWindow;
|
||||
|
||||
class DatabaseWidget;
|
||||
class Entry;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Service;
|
||||
|
||||
class PromptBase : public DBusObjectHelper<PromptBase, PromptAdaptor>
|
||||
class PromptBase : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_PROMPT_LITERAL)
|
||||
public:
|
||||
virtual DBusReturn<void> prompt(const QString& windowId) = 0;
|
||||
Q_INVOKABLE virtual DBusResult prompt(const DBusClientPtr& client, const QString& windowId) = 0;
|
||||
|
||||
virtual DBusReturn<void> dismiss();
|
||||
Q_INVOKABLE virtual DBusResult dismiss();
|
||||
|
||||
template <typename PROMPT, typename... ARGS> static PromptBase* Create(Service* parent, ARGS&&... args)
|
||||
{
|
||||
QScopedPointer<PROMPT> res{new PROMPT(parent, std::forward<ARGS>(args)...)};
|
||||
if (!res->dbus()->registerObject(res.data())) {
|
||||
// internal error;
|
||||
return nullptr;
|
||||
}
|
||||
return res.take();
|
||||
}
|
||||
|
||||
signals:
|
||||
void completed(bool dismissed, const QVariant& result);
|
||||
@ -46,7 +60,6 @@ namespace FdoSecrets
|
||||
protected:
|
||||
explicit PromptBase(Service* parent);
|
||||
|
||||
bool registerSelf();
|
||||
QWindow* findWindow(const QString& windowId);
|
||||
Service* service() const;
|
||||
};
|
||||
@ -60,11 +73,11 @@ namespace FdoSecrets
|
||||
explicit DeleteCollectionPrompt(Service* parent, Collection* coll);
|
||||
|
||||
public:
|
||||
static DBusReturn<DeleteCollectionPrompt*> Create(Service* parent, Collection* coll);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
DBusResult prompt(const DBusClientPtr& client, const QString& windowId) override;
|
||||
|
||||
private:
|
||||
friend class PromptBase;
|
||||
|
||||
QPointer<Collection> m_collection;
|
||||
};
|
||||
|
||||
@ -72,16 +85,17 @@ namespace FdoSecrets
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
explicit CreateCollectionPrompt(Service* parent);
|
||||
explicit CreateCollectionPrompt(Service* parent, QVariantMap properties, QString alias);
|
||||
|
||||
public:
|
||||
static DBusReturn<CreateCollectionPrompt*> Create(Service* parent);
|
||||
DBusResult prompt(const DBusClientPtr& client, const QString& windowId) override;
|
||||
DBusResult dismiss() override;
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
DBusReturn<void> dismiss() override;
|
||||
private:
|
||||
friend class PromptBase;
|
||||
|
||||
signals:
|
||||
void collectionCreated(Collection* coll);
|
||||
QVariantMap m_properties;
|
||||
QString m_alias;
|
||||
};
|
||||
|
||||
class LockCollectionsPrompt : public PromptBase
|
||||
@ -91,35 +105,46 @@ namespace FdoSecrets
|
||||
explicit LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls);
|
||||
|
||||
public:
|
||||
static DBusReturn<LockCollectionsPrompt*> Create(Service* parent, const QList<Collection*>& colls);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
DBusReturn<void> dismiss() override;
|
||||
DBusResult prompt(const DBusClientPtr& client, const QString& windowId) override;
|
||||
DBusResult dismiss() override;
|
||||
|
||||
private:
|
||||
friend class PromptBase;
|
||||
|
||||
QList<QPointer<Collection>> m_collections;
|
||||
QList<QDBusObjectPath> m_locked;
|
||||
};
|
||||
|
||||
class UnlockCollectionsPrompt : public PromptBase
|
||||
class DBusClient;
|
||||
class UnlockPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
explicit UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& coll);
|
||||
explicit UnlockPrompt(Service* parent, const QSet<Collection*>& colls, const QSet<Item*>& items);
|
||||
|
||||
public:
|
||||
static DBusReturn<UnlockCollectionsPrompt*> Create(Service* parent, const QList<Collection*>& coll);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
DBusReturn<void> dismiss() override;
|
||||
DBusResult prompt(const DBusClientPtr& client, const QString& windowId) override;
|
||||
DBusResult dismiss() override;
|
||||
|
||||
private slots:
|
||||
void collectionUnlockFinished(bool accepted);
|
||||
void itemUnlockFinished(const QHash<Entry*, AuthDecision>& results);
|
||||
|
||||
private:
|
||||
void unlockItems();
|
||||
|
||||
friend class PromptBase;
|
||||
|
||||
static constexpr auto FdoSecretsBackend = "FdoSecretsBackend";
|
||||
|
||||
QList<QPointer<Collection>> m_collections;
|
||||
QHash<Collection*, QList<QPointer<Item>>> m_items;
|
||||
QList<QDBusObjectPath> m_unlocked;
|
||||
int m_numRejected = 0;
|
||||
|
||||
// info about calling client
|
||||
QWeakPointer<DBusClient> m_client;
|
||||
QString m_windowId;
|
||||
};
|
||||
|
||||
class Item;
|
||||
@ -130,14 +155,46 @@ namespace FdoSecrets
|
||||
explicit DeleteItemPrompt(Service* parent, Item* item);
|
||||
|
||||
public:
|
||||
static DBusReturn<DeleteItemPrompt*> Create(Service* parent, Item* item);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
DBusResult prompt(const DBusClientPtr& client, const QString& windowId) override;
|
||||
|
||||
private:
|
||||
friend class PromptBase;
|
||||
|
||||
QPointer<Item> m_item;
|
||||
};
|
||||
|
||||
class CreateItemPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
explicit CreateItemPrompt(Service* parent,
|
||||
Collection* coll,
|
||||
QVariantMap properties,
|
||||
Secret secret,
|
||||
QString itemPath,
|
||||
Item* existing);
|
||||
|
||||
public:
|
||||
DBusResult prompt(const DBusClientPtr& client, const QString& windowId) override;
|
||||
DBusResult dismiss() override;
|
||||
private slots:
|
||||
void itemUnlocked(bool dismissed, const QVariant& result);
|
||||
|
||||
private:
|
||||
DBusResult updateItem();
|
||||
|
||||
friend class PromptBase;
|
||||
|
||||
QPointer<Collection> m_coll;
|
||||
QVariantMap m_properties;
|
||||
Secret m_secret;
|
||||
QString m_itemPath;
|
||||
QPointer<Item> m_item;
|
||||
|
||||
QPointer<const Session> m_sess;
|
||||
QWeakPointer<DBusClient> m_client;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_PROMPT_H
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
@ -28,7 +29,6 @@
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QDebug>
|
||||
#include <QSharedPointer>
|
||||
|
||||
@ -39,9 +39,10 @@ namespace
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
QSharedPointer<Service> Service::Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs)
|
||||
QSharedPointer<Service>
|
||||
Service::Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs, QSharedPointer<DBusMgr> dbus)
|
||||
{
|
||||
QSharedPointer<Service> res{new Service(plugin, std::move(dbTabs))};
|
||||
QSharedPointer<Service> res{new Service(plugin, std::move(dbTabs), std::move(dbus))};
|
||||
if (!res->initialize()) {
|
||||
return {};
|
||||
}
|
||||
@ -49,43 +50,25 @@ namespace FdoSecrets
|
||||
}
|
||||
|
||||
Service::Service(FdoSecretsPlugin* plugin,
|
||||
QPointer<DatabaseTabWidget> dbTabs) // clazy: exclude=ctor-missing-parent-argument
|
||||
: DBusObjectHelper(nullptr)
|
||||
QPointer<DatabaseTabWidget> dbTabs,
|
||||
QSharedPointer<DBusMgr> dbus) // clazy: exclude=ctor-missing-parent-argument
|
||||
: DBusObject(std::move(dbus))
|
||||
, m_plugin(plugin)
|
||||
, m_databases(std::move(dbTabs))
|
||||
, m_insideEnsureDefaultAlias(false)
|
||||
, m_serviceWatcher(nullptr)
|
||||
{
|
||||
connect(
|
||||
m_databases, &DatabaseTabWidget::databaseUnlockDialogFinished, this, &Service::doneUnlockDatabaseInDialog);
|
||||
}
|
||||
|
||||
Service::~Service()
|
||||
{
|
||||
QDBusConnection::sessionBus().unregisterService(QStringLiteral(DBUS_SERVICE_SECRET));
|
||||
}
|
||||
Service::~Service() = default;
|
||||
|
||||
bool Service::initialize()
|
||||
{
|
||||
if (!QDBusConnection::sessionBus().registerService(QStringLiteral(DBUS_SERVICE_SECRET))) {
|
||||
plugin()->emitError(
|
||||
tr("Failed to register DBus service at %1.<br/>").arg(QLatin1String(DBUS_SERVICE_SECRET))
|
||||
+ m_plugin->reportExistingService());
|
||||
if (!dbus()->registerObject(this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!registerWithPath(QStringLiteral(DBUS_PATH_SECRETS))) {
|
||||
plugin()->emitError(tr("Failed to register DBus path %1.<br/>").arg(QStringLiteral(DBUS_PATH_SECRETS)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connect to service unregistered signal
|
||||
m_serviceWatcher.reset(new QDBusServiceWatcher());
|
||||
connect(
|
||||
m_serviceWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, this, &Service::dbusServiceUnregistered);
|
||||
|
||||
m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
|
||||
|
||||
// Add existing database tabs
|
||||
for (int idx = 0; idx != m_databases->count(); ++idx) {
|
||||
auto dbWidget = m_databases->databaseWidgetFromIndex(idx);
|
||||
@ -199,161 +182,157 @@ namespace FdoSecrets
|
||||
m_insideEnsureDefaultAlias = false;
|
||||
}
|
||||
|
||||
void Service::dbusServiceUnregistered(const QString& service)
|
||||
DBusResult Service::collections(QList<Collection*>& collections) const
|
||||
{
|
||||
Q_ASSERT(m_serviceWatcher);
|
||||
|
||||
auto removed = m_serviceWatcher->removeWatchedService(service);
|
||||
if (!removed) {
|
||||
qDebug("FdoSecrets: Failed to remove service watcher");
|
||||
}
|
||||
|
||||
Session::CleanupNegotiation(service);
|
||||
auto sess = m_peerToSession.value(service, nullptr);
|
||||
if (sess) {
|
||||
sess->close().okOrDie();
|
||||
}
|
||||
collections = m_collections;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Collection*>> Service::collections() const
|
||||
DBusResult Service::openSession(const DBusClientPtr& client,
|
||||
const QString& algorithm,
|
||||
const QVariant& input,
|
||||
QVariant& output,
|
||||
Session*& result)
|
||||
{
|
||||
return m_collections;
|
||||
}
|
||||
|
||||
DBusReturn<QVariant> Service::openSession(const QString& algorithm, const QVariant& input, Session*& result)
|
||||
{
|
||||
QVariant output;
|
||||
bool incomplete = false;
|
||||
auto peer = callingPeer();
|
||||
|
||||
// watch for service unregister to cleanup
|
||||
Q_ASSERT(m_serviceWatcher);
|
||||
m_serviceWatcher->addWatchedService(peer);
|
||||
|
||||
// negotiate cipher
|
||||
auto ciphers = Session::CreateCiphers(peer, algorithm, input, output, incomplete);
|
||||
bool incomplete = false;
|
||||
auto ciphers = client->negotiateCipher(algorithm, input, output, incomplete);
|
||||
if (incomplete) {
|
||||
result = nullptr;
|
||||
return output;
|
||||
return {};
|
||||
}
|
||||
if (!ciphers) {
|
||||
return DBusReturn<>::Error(QDBusError::NotSupported);
|
||||
return QDBusError::NotSupported;
|
||||
}
|
||||
result = Session::Create(std::move(ciphers), callingPeerName(), this);
|
||||
|
||||
// create session using the negotiated cipher
|
||||
result = Session::Create(std::move(ciphers), client->name(), this);
|
||||
if (!result) {
|
||||
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
|
||||
m_sessions.append(result);
|
||||
m_peerToSession[peer] = result;
|
||||
connect(result, &Session::aboutToClose, this, [this, peer, result]() {
|
||||
emit sessionClosed(result);
|
||||
m_sessions.removeAll(result);
|
||||
m_peerToSession.remove(peer);
|
||||
// remove session when the client disconnects
|
||||
connect(dbus().data(), &DBusMgr::clientDisconnected, result, [result, client](const DBusClientPtr& toRemove) {
|
||||
if (toRemove == client) {
|
||||
result->close().okOrDie();
|
||||
}
|
||||
});
|
||||
emit sessionOpened(result);
|
||||
|
||||
return output;
|
||||
// keep a list of sessions
|
||||
m_sessions.append(result);
|
||||
connect(result, &Session::aboutToClose, this, [this, result]() { m_sessions.removeAll(result); });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<Collection*>
|
||||
Service::createCollection(const QVariantMap& properties, const QString& alias, PromptBase*& prompt)
|
||||
DBusResult Service::createCollection(const QVariantMap& properties,
|
||||
const QString& alias,
|
||||
Collection*& collection,
|
||||
PromptBase*& prompt)
|
||||
{
|
||||
prompt = nullptr;
|
||||
|
||||
// return existing collection if alias is non-empty and exists.
|
||||
auto collection = findCollection(alias);
|
||||
collection = findCollection(alias);
|
||||
if (!collection) {
|
||||
auto cp = CreateCollectionPrompt::Create(this);
|
||||
if (cp.isError()) {
|
||||
return cp;
|
||||
prompt = PromptBase::Create<CreateCollectionPrompt>(this, properties, alias);
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
prompt = cp.value();
|
||||
|
||||
// collection will be created when the prompt completes.
|
||||
// once it's done, we set additional properties on the collection
|
||||
connect(cp.value(),
|
||||
&CreateCollectionPrompt::collectionCreated,
|
||||
cp.value(),
|
||||
[alias, properties](Collection* coll) {
|
||||
coll->setProperties(properties).okOrDie();
|
||||
if (!alias.isEmpty()) {
|
||||
coll->addAlias(alias).okOrDie();
|
||||
}
|
||||
});
|
||||
}
|
||||
return collection;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Item*>> Service::searchItems(const StringStringMap& attributes, QList<Item*>& locked)
|
||||
DBusResult Service::searchItems(const DBusClientPtr& client,
|
||||
const StringStringMap& attributes,
|
||||
QList<Item*>& unlocked,
|
||||
QList<Item*>& locked) const
|
||||
{
|
||||
auto ret = collections();
|
||||
if (ret.isError()) {
|
||||
QList<Collection*> colls;
|
||||
auto ret = collections(colls);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<Item*> unlocked;
|
||||
for (const auto& coll : ret.value()) {
|
||||
auto items = coll->searchItems(attributes);
|
||||
if (items.isError()) {
|
||||
return items;
|
||||
for (const auto& coll : asConst(colls)) {
|
||||
QList<Item*> items;
|
||||
ret = coll->searchItems(attributes, items);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
auto l = coll->locked();
|
||||
if (l.isError()) {
|
||||
return l;
|
||||
}
|
||||
if (l.value()) {
|
||||
locked.append(items.value());
|
||||
} else {
|
||||
unlocked.append(items.value());
|
||||
}
|
||||
}
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> Service::unlock(const QList<DBusObject*>& objects, PromptBase*& prompt)
|
||||
{
|
||||
QSet<Collection*> needUnlock;
|
||||
needUnlock.reserve(objects.size());
|
||||
for (const auto& obj : asConst(objects)) {
|
||||
auto coll = qobject_cast<Collection*>(obj);
|
||||
if (coll) {
|
||||
needUnlock << coll;
|
||||
} else {
|
||||
auto item = qobject_cast<Item*>(obj);
|
||||
if (!item) {
|
||||
continue;
|
||||
// item locked state already covers its collection's locked state
|
||||
for (const auto& item : asConst(items)) {
|
||||
bool l;
|
||||
ret = item->locked(client, l);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (l) {
|
||||
locked.append(item);
|
||||
} else {
|
||||
unlocked.append(item);
|
||||
}
|
||||
// we lock the whole collection for item
|
||||
needUnlock << item->collection();
|
||||
}
|
||||
}
|
||||
|
||||
// return anything already unlocked
|
||||
QList<DBusObject*> unlocked;
|
||||
QList<Collection*> toUnlock;
|
||||
for (const auto& coll : asConst(needUnlock)) {
|
||||
auto l = coll->locked();
|
||||
if (l.isError()) {
|
||||
return l;
|
||||
}
|
||||
if (!l.value()) {
|
||||
unlocked << coll;
|
||||
} else {
|
||||
toUnlock << coll;
|
||||
}
|
||||
}
|
||||
if (!toUnlock.isEmpty()) {
|
||||
auto up = UnlockCollectionsPrompt::Create(this, toUnlock);
|
||||
if (up.isError()) {
|
||||
return up;
|
||||
}
|
||||
prompt = up.value();
|
||||
}
|
||||
return unlocked;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> Service::lock(const QList<DBusObject*>& objects, PromptBase*& prompt)
|
||||
DBusResult Service::unlock(const DBusClientPtr& client,
|
||||
const QList<DBusObject*>& objects,
|
||||
QList<DBusObject*>& unlocked,
|
||||
PromptBase*& prompt)
|
||||
{
|
||||
QSet<Collection*> collectionsToUnlock;
|
||||
QSet<Item*> itemsToUnlock;
|
||||
collectionsToUnlock.reserve(objects.size());
|
||||
itemsToUnlock.reserve(objects.size());
|
||||
|
||||
for (const auto& obj : asConst(objects)) {
|
||||
// the object is either an item or an collection
|
||||
auto item = qobject_cast<Item*>(obj);
|
||||
auto coll = item ? item->collection() : qobject_cast<Collection*>(obj);
|
||||
// either way there should be a collection
|
||||
if (!coll) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool collLocked{false}, itemLocked{false};
|
||||
// if the collection needs unlock
|
||||
auto ret = coll->locked(collLocked);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (collLocked) {
|
||||
collectionsToUnlock << coll;
|
||||
}
|
||||
|
||||
if (item) {
|
||||
// item may also need unlock
|
||||
ret = item->locked(client, itemLocked);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (itemLocked) {
|
||||
itemsToUnlock << item;
|
||||
}
|
||||
}
|
||||
|
||||
// both collection and item are not locked
|
||||
if (!collLocked && !itemLocked) {
|
||||
unlocked << obj;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collectionsToUnlock.isEmpty() || !itemsToUnlock.isEmpty()) {
|
||||
prompt = PromptBase::Create<UnlockPrompt>(this, collectionsToUnlock, itemsToUnlock);
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusResult Service::lock(const QList<DBusObject*>& objects, QList<DBusObject*>& locked, PromptBase*& prompt)
|
||||
{
|
||||
QSet<Collection*> needLock;
|
||||
needLock.reserve(objects.size());
|
||||
@ -372,64 +351,62 @@ namespace FdoSecrets
|
||||
}
|
||||
|
||||
// return anything already locked
|
||||
QList<DBusObject*> locked;
|
||||
QList<Collection*> toLock;
|
||||
for (const auto& coll : asConst(needLock)) {
|
||||
auto l = coll->locked();
|
||||
if (l.isError()) {
|
||||
return l;
|
||||
bool l;
|
||||
auto ret = coll->locked(l);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
if (l.value()) {
|
||||
if (l) {
|
||||
locked << coll;
|
||||
} else {
|
||||
toLock << coll;
|
||||
}
|
||||
}
|
||||
if (!toLock.isEmpty()) {
|
||||
auto lp = LockCollectionsPrompt::Create(this, toLock);
|
||||
if (lp.isError()) {
|
||||
return lp;
|
||||
prompt = PromptBase::Create<LockCollectionsPrompt>(this, toLock);
|
||||
if (!prompt) {
|
||||
return QDBusError::InternalError;
|
||||
}
|
||||
prompt = lp.value();
|
||||
}
|
||||
return locked;
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QHash<Item*, SecretStruct>> Service::getSecrets(const QList<Item*>& items, Session* session)
|
||||
DBusResult Service::getSecrets(const DBusClientPtr& client,
|
||||
const QList<Item*>& items,
|
||||
Session* session,
|
||||
ItemSecretMap& secrets) const
|
||||
{
|
||||
if (!session) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SESSION);
|
||||
}
|
||||
|
||||
QHash<Item*, SecretStruct> res;
|
||||
|
||||
for (const auto& item : asConst(items)) {
|
||||
auto ret = item->getSecret(session);
|
||||
if (ret.isError()) {
|
||||
auto ret = item->getSecretNoNotification(client, session, secrets[item]);
|
||||
if (ret.err()) {
|
||||
return ret;
|
||||
}
|
||||
res[item] = std::move(ret).value();
|
||||
}
|
||||
if (calledFromDBus()) {
|
||||
plugin()->emitRequestShowNotification(
|
||||
tr(R"(%n Entry(s) was used by %1)", "%1 is the name of an application", res.size())
|
||||
.arg(callingPeerName()));
|
||||
}
|
||||
return res;
|
||||
plugin()->emitRequestShowNotification(
|
||||
tr(R"(%n Entry(s) was used by %1)", "%1 is the name of an application", secrets.size())
|
||||
.arg(client->name()));
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<Collection*> Service::readAlias(const QString& name)
|
||||
DBusResult Service::readAlias(const QString& name, Collection*& collection) const
|
||||
{
|
||||
return findCollection(name);
|
||||
collection = findCollection(name);
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Service::setAlias(const QString& name, Collection* collection)
|
||||
DBusResult Service::setAlias(const QString& name, Collection* collection)
|
||||
{
|
||||
if (!collection) {
|
||||
// remove alias name from its collection
|
||||
collection = findCollection(name);
|
||||
if (!collection) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT);
|
||||
}
|
||||
return collection->removeAlias(name);
|
||||
}
|
||||
@ -481,7 +458,7 @@ namespace FdoSecrets
|
||||
return m_dbToCollection.value(db, nullptr);
|
||||
}
|
||||
|
||||
const QList<Session*> Service::sessions() const
|
||||
QList<Session*> Service::sessions() const
|
||||
{
|
||||
return m_sessions;
|
||||
}
|
||||
|
@ -18,18 +18,14 @@
|
||||
#ifndef KEEPASSXC_FDOSECRETS_SERVICE_H
|
||||
#define KEEPASSXC_FDOSECRETS_SERVICE_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/adaptors/ServiceAdaptor.h"
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QVariant>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QDBusServiceWatcher;
|
||||
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
class Group;
|
||||
@ -42,14 +38,14 @@ namespace FdoSecrets
|
||||
class Collection;
|
||||
class Item;
|
||||
class PromptBase;
|
||||
class ServiceAdaptor;
|
||||
class Session;
|
||||
|
||||
class Service : public DBusObjectHelper<Service, ServiceAdaptor> // clazy: exclude=ctor-missing-parent-argument
|
||||
class Service : public DBusObject // clazy: exclude=ctor-missing-parent-argument
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_SERVICE_LITERAL)
|
||||
|
||||
explicit Service(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs);
|
||||
explicit Service(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs, QSharedPointer<DBusMgr> dbus);
|
||||
|
||||
public:
|
||||
/**
|
||||
@ -58,38 +54,51 @@ namespace FdoSecrets
|
||||
* This may be caused by
|
||||
* - failed initialization
|
||||
*/
|
||||
static QSharedPointer<Service> Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs);
|
||||
static QSharedPointer<Service>
|
||||
Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs, QSharedPointer<DBusMgr> dbus);
|
||||
~Service() override;
|
||||
|
||||
DBusReturn<QVariant> openSession(const QString& algorithm, const QVariant& input, Session*& result);
|
||||
DBusReturn<Collection*>
|
||||
createCollection(const QVariantMap& properties, const QString& alias, PromptBase*& prompt);
|
||||
DBusReturn<const QList<Item*>> searchItems(const StringStringMap& attributes, QList<Item*>& locked);
|
||||
Q_INVOKABLE DBusResult openSession(const DBusClientPtr& client,
|
||||
const QString& algorithm,
|
||||
const QVariant& input,
|
||||
QVariant& output,
|
||||
Session*& result);
|
||||
Q_INVOKABLE DBusResult createCollection(const QVariantMap& properties,
|
||||
const QString& alias,
|
||||
Collection*& collection,
|
||||
PromptBase*& prompt);
|
||||
Q_INVOKABLE DBusResult searchItems(const DBusClientPtr& client,
|
||||
const StringStringMap& attributes,
|
||||
QList<Item*>& unlocked,
|
||||
QList<Item*>& locked) const;
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> unlock(const QList<DBusObject*>& objects, PromptBase*& prompt);
|
||||
Q_INVOKABLE DBusResult unlock(const DBusClientPtr& client,
|
||||
const QList<DBusObject*>& objects,
|
||||
QList<DBusObject*>& unlocked,
|
||||
PromptBase*& prompt);
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> lock(const QList<DBusObject*>& objects, PromptBase*& prompt);
|
||||
Q_INVOKABLE DBusResult lock(const QList<DBusObject*>& objects, QList<DBusObject*>& locked, PromptBase*& prompt);
|
||||
|
||||
DBusReturn<const QHash<Item*, SecretStruct>> getSecrets(const QList<Item*>& items, Session* session);
|
||||
Q_INVOKABLE DBusResult getSecrets(const DBusClientPtr& client,
|
||||
const QList<Item*>& items,
|
||||
Session* session,
|
||||
ItemSecretMap& secrets) const;
|
||||
|
||||
DBusReturn<Collection*> readAlias(const QString& name);
|
||||
Q_INVOKABLE DBusResult readAlias(const QString& name, Collection*& collection) const;
|
||||
|
||||
DBusReturn<void> setAlias(const QString& name, Collection* collection);
|
||||
Q_INVOKABLE DBusResult setAlias(const QString& name, Collection* collection);
|
||||
|
||||
/**
|
||||
* List of collections
|
||||
* @return
|
||||
*/
|
||||
DBusReturn<const QList<Collection*>> collections() const;
|
||||
Q_INVOKABLE DBUS_PROPERTY DBusResult collections(QList<Collection*>& collections) const;
|
||||
|
||||
signals:
|
||||
void collectionCreated(Collection* collection);
|
||||
void collectionDeleted(Collection* collection);
|
||||
void collectionChanged(Collection* collection);
|
||||
|
||||
void sessionOpened(Session* sess);
|
||||
void sessionClosed(Session* sess);
|
||||
|
||||
/**
|
||||
* Finish signal for async action doUnlockDatabaseInDialog
|
||||
* @param accepted If false, the action is canceled by the user
|
||||
@ -102,7 +111,7 @@ namespace FdoSecrets
|
||||
* List of sessions
|
||||
* @return
|
||||
*/
|
||||
const QList<Session*> sessions() const;
|
||||
QList<Session*> sessions() const;
|
||||
|
||||
FdoSecretsPlugin* plugin() const
|
||||
{
|
||||
@ -121,7 +130,6 @@ namespace FdoSecrets
|
||||
void doUnlockDatabaseInDialog(DatabaseWidget* dbWidget);
|
||||
|
||||
private slots:
|
||||
void dbusServiceUnregistered(const QString& service);
|
||||
void ensureDefaultAlias();
|
||||
|
||||
void onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal);
|
||||
@ -158,11 +166,8 @@ namespace FdoSecrets
|
||||
QHash<const DatabaseWidget*, Collection*> m_dbToCollection;
|
||||
|
||||
QList<Session*> m_sessions;
|
||||
QHash<QString, Session*> m_peerToSession;
|
||||
|
||||
bool m_insideEnsureDefaultAlias;
|
||||
|
||||
std::unique_ptr<QDBusServiceWatcher> m_serviceWatcher;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
@ -14,53 +14,36 @@
|
||||
* 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 "Session.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
|
||||
#include "core/Tools.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
QHash<QString, QVariant> Session::negotiationState;
|
||||
|
||||
Session* Session::Create(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent)
|
||||
Session* Session::Create(QSharedPointer<CipherPair> cipher, const QString& peer, Service* parent)
|
||||
{
|
||||
QScopedPointer<Session> res{new Session(std::move(cipher), peer, parent)};
|
||||
|
||||
if (!res->registerSelf()) {
|
||||
if (!res->dbus()->registerObject(res.data())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return res.take();
|
||||
}
|
||||
|
||||
Session::Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent)
|
||||
: DBusObjectHelper(parent)
|
||||
Session::Session(QSharedPointer<CipherPair> cipher, const QString& peer, Service* parent)
|
||||
: DBusObject(parent)
|
||||
, m_cipher(std::move(cipher))
|
||||
, m_peer(peer)
|
||||
, m_id(QUuid::createUuid())
|
||||
{
|
||||
}
|
||||
|
||||
bool Session::registerSelf()
|
||||
{
|
||||
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_SESSION).arg(p()->objectPath().path(), id());
|
||||
bool ok = registerWithPath(path);
|
||||
if (!ok) {
|
||||
service()->plugin()->emitError(tr("Failed to register session on DBus at path '%1'").arg(path));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void Session::CleanupNegotiation(const QString& peer)
|
||||
{
|
||||
negotiationState.remove(peer);
|
||||
}
|
||||
|
||||
DBusReturn<void> Session::close()
|
||||
DBusResult Session::close()
|
||||
{
|
||||
emit aboutToClose();
|
||||
deleteLater();
|
||||
@ -83,48 +66,16 @@ namespace FdoSecrets
|
||||
return qobject_cast<Service*>(parent());
|
||||
}
|
||||
|
||||
std::unique_ptr<CipherPair> Session::CreateCiphers(const QString& peer,
|
||||
const QString& algorithm,
|
||||
const QVariant& input,
|
||||
QVariant& output,
|
||||
bool& incomplete)
|
||||
{
|
||||
Q_UNUSED(peer);
|
||||
incomplete = false;
|
||||
|
||||
std::unique_ptr<CipherPair> cipher{};
|
||||
if (algorithm == QLatin1String(PlainCipher::Algorithm)) {
|
||||
cipher.reset(new PlainCipher);
|
||||
} else if (algorithm == QLatin1String(DhIetf1024Sha256Aes128CbcPkcs7::Algorithm)) {
|
||||
QByteArray clientPublicKey = input.toByteArray();
|
||||
cipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7(clientPublicKey));
|
||||
} else {
|
||||
// error notSupported
|
||||
}
|
||||
|
||||
if (!cipher) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!cipher->isValid()) {
|
||||
qWarning() << "FdoSecrets: Error creating cipher";
|
||||
return {};
|
||||
}
|
||||
|
||||
output = cipher->negotiationOutput();
|
||||
return cipher;
|
||||
}
|
||||
|
||||
SecretStruct Session::encode(const SecretStruct& input) const
|
||||
Secret Session::encode(const Secret& input) const
|
||||
{
|
||||
auto output = m_cipher->encrypt(input);
|
||||
output.session = objectPath();
|
||||
output.session = this;
|
||||
return output;
|
||||
}
|
||||
|
||||
SecretStruct Session::decode(const SecretStruct& input) const
|
||||
Secret Session::decode(const Secret& input) const
|
||||
{
|
||||
Q_ASSERT(input.session == this);
|
||||
return m_cipher->decrypt(input);
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
@ -18,36 +18,24 @@
|
||||
#ifndef KEEPASSXC_FDOSECRETS_SESSION_H
|
||||
#define KEEPASSXC_FDOSECRETS_SESSION_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
#include "fdosecrets/objects/adaptors/SessionAdaptor.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
#include <QVariant>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class CipherPair;
|
||||
class Session : public DBusObjectHelper<Session, SessionAdaptor>
|
||||
class Session : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_SESSION_LITERAL)
|
||||
|
||||
explicit Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent);
|
||||
explicit Session(QSharedPointer<CipherPair> cipher, const QString& peer, Service* parent);
|
||||
|
||||
public:
|
||||
static std::unique_ptr<CipherPair> CreateCiphers(const QString& peer,
|
||||
const QString& algorithm,
|
||||
const QVariant& input,
|
||||
QVariant& output,
|
||||
bool& incomplete);
|
||||
static void CleanupNegotiation(const QString& peer);
|
||||
|
||||
/**
|
||||
* @brief Create a new instance of `Session`.
|
||||
* @param cipher the negotiated cipher
|
||||
@ -57,23 +45,23 @@ namespace FdoSecrets
|
||||
* This may be caused by
|
||||
* - DBus path registration error
|
||||
*/
|
||||
static Session* Create(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent);
|
||||
static Session* Create(QSharedPointer<CipherPair> cipher, const QString& peer, Service* parent);
|
||||
|
||||
DBusReturn<void> close();
|
||||
Q_INVOKABLE DBusResult close();
|
||||
|
||||
/**
|
||||
* Encode the secret struct. Note only the value field is encoded.
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
SecretStruct encode(const SecretStruct& input) const;
|
||||
Secret encode(const Secret& input) const;
|
||||
|
||||
/**
|
||||
* Decode the secret struct.
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
SecretStruct decode(const SecretStruct& input) const;
|
||||
Secret decode(const Secret& input) const;
|
||||
|
||||
/**
|
||||
* The peer application that opened this session
|
||||
@ -93,14 +81,9 @@ namespace FdoSecrets
|
||||
void aboutToClose();
|
||||
|
||||
private:
|
||||
bool registerSelf();
|
||||
|
||||
private:
|
||||
std::unique_ptr<CipherPair> m_cipher;
|
||||
QSharedPointer<CipherPair> m_cipher;
|
||||
QString m_peer;
|
||||
QUuid m_id;
|
||||
|
||||
static QHash<QString, QVariant> negotiationState;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
@ -149,9 +149,9 @@ namespace FdoSecrets
|
||||
return OKM;
|
||||
}
|
||||
|
||||
SecretStruct DhIetf1024Sha256Aes128CbcPkcs7::encrypt(const SecretStruct& input)
|
||||
Secret DhIetf1024Sha256Aes128CbcPkcs7::encrypt(const Secret& input)
|
||||
{
|
||||
SecretStruct output = input;
|
||||
Secret output = input;
|
||||
output.value.clear();
|
||||
output.parameters.clear();
|
||||
|
||||
@ -187,7 +187,7 @@ namespace FdoSecrets
|
||||
return input;
|
||||
}
|
||||
|
||||
SecretStruct DhIetf1024Sha256Aes128CbcPkcs7::decrypt(const SecretStruct& input)
|
||||
Secret DhIetf1024Sha256Aes128CbcPkcs7::decrypt(const Secret& input)
|
||||
{
|
||||
auto IV = input.parameters;
|
||||
SymmetricCipher decrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
@ -196,7 +196,7 @@ namespace FdoSecrets
|
||||
return input;
|
||||
}
|
||||
bool ok;
|
||||
SecretStruct output = input;
|
||||
Secret output = input;
|
||||
output.parameters.clear();
|
||||
output.value = decrypter.process(input.value, &ok);
|
||||
|
||||
|
@ -33,8 +33,8 @@ namespace FdoSecrets
|
||||
public:
|
||||
CipherPair() = default;
|
||||
virtual ~CipherPair() = default;
|
||||
virtual SecretStruct encrypt(const SecretStruct& input) = 0;
|
||||
virtual SecretStruct decrypt(const SecretStruct& input) = 0;
|
||||
virtual Secret encrypt(const Secret& input) = 0;
|
||||
virtual Secret decrypt(const Secret& input) = 0;
|
||||
virtual bool isValid() const = 0;
|
||||
virtual QVariant negotiationOutput() const = 0;
|
||||
};
|
||||
@ -46,12 +46,12 @@ namespace FdoSecrets
|
||||
static constexpr const char Algorithm[] = "plain";
|
||||
|
||||
PlainCipher() = default;
|
||||
SecretStruct encrypt(const SecretStruct& input) override
|
||||
Secret encrypt(const Secret& input) override
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
SecretStruct decrypt(const SecretStruct& input) override
|
||||
Secret decrypt(const Secret& input) override
|
||||
{
|
||||
return input;
|
||||
}
|
||||
@ -120,9 +120,9 @@ namespace FdoSecrets
|
||||
|
||||
explicit DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes);
|
||||
|
||||
SecretStruct encrypt(const SecretStruct& input) override;
|
||||
Secret encrypt(const Secret& input) override;
|
||||
|
||||
SecretStruct decrypt(const SecretStruct& input) override;
|
||||
Secret decrypt(const Secret& input) override;
|
||||
|
||||
bool isValid() const override;
|
||||
|
||||
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "CollectionAdaptor.h"
|
||||
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
CollectionAdaptor::CollectionAdaptor(Collection* parent)
|
||||
: DBusAdaptor(parent)
|
||||
{
|
||||
// p() isn't ready yet as this is called in Parent's constructor
|
||||
connect(parent, &Collection::itemCreated, this, [this](const Item* item) {
|
||||
emit ItemCreated(objectPathSafe(item));
|
||||
});
|
||||
connect(parent, &Collection::itemDeleted, this, [this](const Item* item) {
|
||||
emit ItemDeleted(objectPathSafe(item));
|
||||
});
|
||||
connect(parent, &Collection::itemChanged, this, [this](const Item* item) {
|
||||
emit ItemChanged(objectPathSafe(item));
|
||||
});
|
||||
}
|
||||
|
||||
const QList<QDBusObjectPath> CollectionAdaptor::items() const
|
||||
{
|
||||
return objectsToPath(p()->items().valueOrHandle(p()));
|
||||
}
|
||||
|
||||
QString CollectionAdaptor::label() const
|
||||
{
|
||||
return p()->label().valueOrHandle(p());
|
||||
}
|
||||
|
||||
void CollectionAdaptor::setLabel(const QString& label)
|
||||
{
|
||||
p()->setLabel(label).handle(p());
|
||||
}
|
||||
|
||||
bool CollectionAdaptor::locked() const
|
||||
{
|
||||
return p()->locked().valueOrHandle(p());
|
||||
}
|
||||
|
||||
qulonglong CollectionAdaptor::created() const
|
||||
{
|
||||
return p()->created().valueOrHandle(p());
|
||||
}
|
||||
|
||||
qulonglong CollectionAdaptor::modified() const
|
||||
{
|
||||
return p()->modified().valueOrHandle(p());
|
||||
}
|
||||
|
||||
QDBusObjectPath CollectionAdaptor::Delete()
|
||||
{
|
||||
return objectPathSafe(p()->deleteCollection().valueOrHandle(p()));
|
||||
}
|
||||
|
||||
QList<QDBusObjectPath> CollectionAdaptor::SearchItems(const StringStringMap& attributes)
|
||||
{
|
||||
return objectsToPath(p()->searchItems(attributes).valueOrHandle(p()));
|
||||
}
|
||||
|
||||
QDBusObjectPath CollectionAdaptor::CreateItem(const QVariantMap& properties,
|
||||
const SecretStruct& secret,
|
||||
bool replace,
|
||||
QDBusObjectPath& prompt)
|
||||
{
|
||||
PromptBase* pp = nullptr;
|
||||
auto item = p()->createItem(properties, secret, replace, pp).valueOrHandle(p());
|
||||
prompt = objectPathSafe(pp);
|
||||
return objectPathSafe(item);
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_COLLECTIONADAPTOR_H
|
||||
#define KEEPASSXC_FDOSECRETS_COLLECTIONADAPTOR_H
|
||||
|
||||
#include "fdosecrets/objects/adaptors/DBusAdaptor.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Collection;
|
||||
class CollectionAdaptor : public DBusAdaptor<Collection>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_COLLECTION)
|
||||
|
||||
Q_PROPERTY(QList<QDBusObjectPath> Items READ items)
|
||||
Q_PROPERTY(QString Label READ label WRITE setLabel)
|
||||
Q_PROPERTY(bool Locked READ locked)
|
||||
Q_PROPERTY(qulonglong Created READ created)
|
||||
Q_PROPERTY(qulonglong Modified READ modified)
|
||||
|
||||
public:
|
||||
explicit CollectionAdaptor(Collection* parent);
|
||||
~CollectionAdaptor() override = default;
|
||||
|
||||
const QList<QDBusObjectPath> items() const;
|
||||
|
||||
QString label() const;
|
||||
void setLabel(const QString& label);
|
||||
|
||||
bool locked() const;
|
||||
|
||||
qulonglong created() const;
|
||||
|
||||
qulonglong modified() const;
|
||||
|
||||
public slots:
|
||||
QDBusObjectPath Delete();
|
||||
QList<QDBusObjectPath> SearchItems(const StringStringMap& attributes);
|
||||
QDBusObjectPath CreateItem(const QVariantMap& properties,
|
||||
const FdoSecrets::SecretStruct& secret,
|
||||
bool replace,
|
||||
QDBusObjectPath& prompt);
|
||||
|
||||
signals:
|
||||
void ItemCreated(const QDBusObjectPath& item);
|
||||
void ItemDeleted(const QDBusObjectPath& item);
|
||||
void ItemChanged(const QDBusObjectPath& item);
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_COLLECTIONADAPTOR_H
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_DBUSADAPTOR_H
|
||||
#define KEEPASSXC_FDOSECRETS_DBUSADAPTOR_H
|
||||
|
||||
#include "fdosecrets/objects/DBusReturn.h"
|
||||
#include "fdosecrets/objects/DBusTypes.h"
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A common adapter class
|
||||
*/
|
||||
template <typename Parent> class DBusAdaptor : public QDBusAbstractAdaptor
|
||||
{
|
||||
public:
|
||||
explicit DBusAdaptor(Parent* parent = nullptr)
|
||||
: QDBusAbstractAdaptor(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~DBusAdaptor() override = default;
|
||||
|
||||
protected:
|
||||
Parent* p() const
|
||||
{
|
||||
return qobject_cast<Parent*>(parent());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSADAPTOR_H
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "ItemAdaptor.h"
|
||||
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
ItemAdaptor::ItemAdaptor(Item* parent)
|
||||
: DBusAdaptor(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool ItemAdaptor::locked() const
|
||||
{
|
||||
return p()->locked().valueOrHandle(p());
|
||||
}
|
||||
|
||||
const StringStringMap ItemAdaptor::attributes() const
|
||||
{
|
||||
return p()->attributes().valueOrHandle(p());
|
||||
}
|
||||
|
||||
void ItemAdaptor::setAttributes(const StringStringMap& attrs)
|
||||
{
|
||||
p()->setAttributes(attrs).handle(p());
|
||||
}
|
||||
|
||||
QString ItemAdaptor::label() const
|
||||
{
|
||||
return p()->label().valueOrHandle(p());
|
||||
}
|
||||
|
||||
void ItemAdaptor::setLabel(const QString& label)
|
||||
{
|
||||
p()->setLabel(label).handle(p());
|
||||
}
|
||||
|
||||
qulonglong ItemAdaptor::created() const
|
||||
{
|
||||
return p()->created().valueOrHandle(p());
|
||||
}
|
||||
|
||||
qulonglong ItemAdaptor::modified() const
|
||||
{
|
||||
return p()->modified().valueOrHandle(p());
|
||||
}
|
||||
|
||||
QDBusObjectPath ItemAdaptor::Delete()
|
||||
{
|
||||
auto prompt = p()->deleteItem().valueOrHandle(p());
|
||||
return objectPathSafe(prompt);
|
||||
}
|
||||
|
||||
SecretStruct ItemAdaptor::GetSecret(const QDBusObjectPath& session)
|
||||
{
|
||||
return p()->getSecret(pathToObject<Session>(session)).valueOrHandle(p());
|
||||
}
|
||||
|
||||
void ItemAdaptor::SetSecret(const SecretStruct& secret)
|
||||
{
|
||||
p()->setSecret(secret).handle(p());
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_ITEMADAPTOR_H
|
||||
#define KEEPASSXC_FDOSECRETS_ITEMADAPTOR_H
|
||||
|
||||
#include "fdosecrets/objects/adaptors/DBusAdaptor.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Item;
|
||||
class ItemAdaptor : public DBusAdaptor<Item>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_ITEM)
|
||||
|
||||
Q_PROPERTY(bool Locked READ locked)
|
||||
Q_PROPERTY(StringStringMap Attributes READ attributes WRITE setAttributes)
|
||||
Q_PROPERTY(QString Label READ label WRITE setLabel)
|
||||
Q_PROPERTY(qulonglong Created READ created)
|
||||
Q_PROPERTY(qulonglong Modified READ modified)
|
||||
|
||||
public:
|
||||
explicit ItemAdaptor(Item* parent);
|
||||
~ItemAdaptor() override = default;
|
||||
|
||||
bool locked() const;
|
||||
|
||||
const StringStringMap attributes() const;
|
||||
void setAttributes(const StringStringMap& attrs);
|
||||
|
||||
QString label() const;
|
||||
void setLabel(const QString& label);
|
||||
|
||||
qulonglong created() const;
|
||||
|
||||
qulonglong modified() const;
|
||||
|
||||
public slots:
|
||||
QDBusObjectPath Delete();
|
||||
FdoSecrets::SecretStruct GetSecret(const QDBusObjectPath& session);
|
||||
void SetSecret(const FdoSecrets::SecretStruct& secret);
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_ITEMADAPTOR_H
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "PromptAdaptor.h"
|
||||
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
PromptAdaptor::PromptAdaptor(PromptBase* parent)
|
||||
: DBusAdaptor(parent)
|
||||
{
|
||||
// p() isn't ready yet as this is called in Parent's constructor
|
||||
connect(parent, &PromptBase::completed, this, [this](bool dismissed, QVariant result) {
|
||||
// make sure the result contains a valid value, otherwise QDBusVariant refuses to marshall it.
|
||||
if (!result.isValid()) {
|
||||
result = QString{};
|
||||
}
|
||||
emit Completed(dismissed, QDBusVariant(std::move(result)));
|
||||
});
|
||||
}
|
||||
|
||||
void PromptAdaptor::Prompt(const QString& windowId)
|
||||
{
|
||||
p()->prompt(windowId).handle(p());
|
||||
}
|
||||
|
||||
void PromptAdaptor::Dismiss()
|
||||
{
|
||||
p()->dismiss().handle(p());
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_PROMPTADAPTOR_H
|
||||
#define KEEPASSXC_FDOSECRETS_PROMPTADAPTOR_H
|
||||
|
||||
#include "fdosecrets/objects/adaptors/DBusAdaptor.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class PromptBase;
|
||||
class PromptAdaptor : public DBusAdaptor<PromptBase>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_PROMPT)
|
||||
|
||||
public:
|
||||
explicit PromptAdaptor(PromptBase* parent);
|
||||
~PromptAdaptor() override = default;
|
||||
|
||||
public slots:
|
||||
void Prompt(const QString& windowId);
|
||||
void Dismiss();
|
||||
|
||||
signals:
|
||||
void Completed(bool dismissed, const QDBusVariant& result);
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_PROMPTADAPTOR_H
|
@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "ServiceAdaptor.h"
|
||||
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
ServiceAdaptor::ServiceAdaptor(Service* parent)
|
||||
: DBusAdaptor(parent)
|
||||
{
|
||||
// p() isn't ready yet as this is called in Parent's constructor
|
||||
connect(parent, &Service::collectionCreated, this, [this](Collection* coll) {
|
||||
emit CollectionCreated(objectPathSafe(coll));
|
||||
});
|
||||
connect(parent, &Service::collectionDeleted, this, [this](Collection* coll) {
|
||||
emit CollectionDeleted(objectPathSafe(coll));
|
||||
});
|
||||
connect(parent, &Service::collectionChanged, this, [this](Collection* coll) {
|
||||
emit CollectionChanged(objectPathSafe(coll));
|
||||
});
|
||||
}
|
||||
|
||||
const QList<QDBusObjectPath> ServiceAdaptor::collections() const
|
||||
{
|
||||
auto colls = p()->collections().valueOrHandle(p());
|
||||
return objectsToPath(std::move(colls));
|
||||
}
|
||||
|
||||
QDBusVariant
|
||||
ServiceAdaptor::OpenSession(const QString& algorithm, const QDBusVariant& input, QDBusObjectPath& result)
|
||||
{
|
||||
Session* session = nullptr;
|
||||
auto output = p()->openSession(algorithm, input.variant(), session).valueOrHandle(p());
|
||||
result = objectPathSafe(session);
|
||||
return QDBusVariant(std::move(output));
|
||||
}
|
||||
|
||||
QDBusObjectPath
|
||||
ServiceAdaptor::CreateCollection(const QVariantMap& properties, const QString& alias, QDBusObjectPath& prompt)
|
||||
{
|
||||
PromptBase* pp;
|
||||
auto coll = p()->createCollection(properties, alias, pp).valueOrHandle(p());
|
||||
prompt = objectPathSafe(pp);
|
||||
return objectPathSafe(coll);
|
||||
}
|
||||
|
||||
const QList<QDBusObjectPath> ServiceAdaptor::SearchItems(const StringStringMap& attributes,
|
||||
QList<QDBusObjectPath>& locked)
|
||||
{
|
||||
QList<Item*> lockedItems, unlockedItems;
|
||||
unlockedItems = p()->searchItems(attributes, lockedItems).valueOrHandle(p());
|
||||
locked = objectsToPath(lockedItems);
|
||||
return objectsToPath(unlockedItems);
|
||||
}
|
||||
|
||||
const QList<QDBusObjectPath> ServiceAdaptor::Unlock(const QList<QDBusObjectPath>& paths, QDBusObjectPath& prompt)
|
||||
{
|
||||
auto objects = pathsToObject<DBusObject>(paths);
|
||||
if (!paths.isEmpty() && objects.isEmpty()) {
|
||||
DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT)).handle(p());
|
||||
return {};
|
||||
}
|
||||
|
||||
PromptBase* pp = nullptr;
|
||||
auto unlocked = p()->unlock(objects, pp).valueOrHandle(p());
|
||||
|
||||
prompt = objectPathSafe(pp);
|
||||
return objectsToPath(unlocked);
|
||||
}
|
||||
|
||||
const QList<QDBusObjectPath> ServiceAdaptor::Lock(const QList<QDBusObjectPath>& paths, QDBusObjectPath& prompt)
|
||||
{
|
||||
auto objects = pathsToObject<DBusObject>(paths);
|
||||
if (!paths.isEmpty() && objects.isEmpty()) {
|
||||
DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT)).handle(p());
|
||||
return {};
|
||||
}
|
||||
|
||||
PromptBase* pp = nullptr;
|
||||
auto locked = p()->lock(objects, pp).valueOrHandle(p());
|
||||
|
||||
prompt = objectPathSafe(pp);
|
||||
return objectsToPath(locked);
|
||||
}
|
||||
|
||||
const ObjectPathSecretMap ServiceAdaptor::GetSecrets(const QList<QDBusObjectPath>& items,
|
||||
const QDBusObjectPath& session)
|
||||
{
|
||||
auto itemObjects = pathsToObject<Item>(items);
|
||||
if (!items.isEmpty() && itemObjects.isEmpty()) {
|
||||
DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT)).handle(p());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto secrets = p()->getSecrets(pathsToObject<Item>(items), pathToObject<Session>(session)).valueOrHandle(p());
|
||||
|
||||
ObjectPathSecretMap res;
|
||||
auto iter = secrets.begin();
|
||||
while (iter != secrets.end()) {
|
||||
res[objectPathSafe(iter.key())] = std::move(iter.value());
|
||||
++iter;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
QDBusObjectPath ServiceAdaptor::ReadAlias(const QString& name)
|
||||
{
|
||||
auto coll = p()->readAlias(name).valueOrHandle(p());
|
||||
return objectPathSafe(coll);
|
||||
}
|
||||
|
||||
void ServiceAdaptor::SetAlias(const QString& name, const QDBusObjectPath& collection)
|
||||
{
|
||||
p()->setAlias(name, pathToObject<Collection>(collection)).handle(p());
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_SECRETSERVICEDBUS_H
|
||||
#define KEEPASSXC_FDOSECRETS_SECRETSERVICEDBUS_H
|
||||
|
||||
#include "DBusAdaptor.h"
|
||||
|
||||
#include <QDBusObjectPath>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
/**
|
||||
* @brief Adapter class for interface org.freedesktop.Secret.Service
|
||||
*/
|
||||
class Service;
|
||||
class ServiceAdaptor : public DBusAdaptor<Service>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_SERVICE)
|
||||
|
||||
Q_PROPERTY(QList<QDBusObjectPath> Collections READ collections)
|
||||
|
||||
public:
|
||||
explicit ServiceAdaptor(Service* parent);
|
||||
~ServiceAdaptor() override = default;
|
||||
|
||||
const QList<QDBusObjectPath> collections() const;
|
||||
|
||||
public slots:
|
||||
QDBusVariant OpenSession(const QString& algorithm, const QDBusVariant& input, QDBusObjectPath& result);
|
||||
|
||||
QDBusObjectPath CreateCollection(const QVariantMap& properties, const QString& alias, QDBusObjectPath& prompt);
|
||||
|
||||
const QList<QDBusObjectPath> SearchItems(const StringStringMap& attributes, QList<QDBusObjectPath>& locked);
|
||||
|
||||
const QList<QDBusObjectPath> Unlock(const QList<QDBusObjectPath>& paths, QDBusObjectPath& prompt);
|
||||
|
||||
const QList<QDBusObjectPath> Lock(const QList<QDBusObjectPath>& paths, QDBusObjectPath& prompt);
|
||||
|
||||
const ObjectPathSecretMap GetSecrets(const QList<QDBusObjectPath>& items, const QDBusObjectPath& session);
|
||||
|
||||
QDBusObjectPath ReadAlias(const QString& name);
|
||||
|
||||
void SetAlias(const QString& name, const QDBusObjectPath& collection);
|
||||
|
||||
signals:
|
||||
void CollectionCreated(const QDBusObjectPath& collection);
|
||||
|
||||
void CollectionDeleted(const QDBusObjectPath& collection);
|
||||
|
||||
void CollectionChanged(const QDBusObjectPath& collection);
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_SECRETSERVICEDBUS_H
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "SessionAdaptor.h"
|
||||
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
SessionAdaptor::SessionAdaptor(Session* parent)
|
||||
: DBusAdaptor(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void SessionAdaptor::Close()
|
||||
{
|
||||
p()->close().handle(p());
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_SESSIONADAPTOR_H
|
||||
#define KEEPASSXC_FDOSECRETS_SESSIONADAPTOR_H
|
||||
|
||||
#include "fdosecrets/objects/adaptors/DBusAdaptor.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Session;
|
||||
class SessionAdaptor : public DBusAdaptor<Session>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_SESSION)
|
||||
|
||||
public:
|
||||
explicit SessionAdaptor(Session* parent);
|
||||
~SessionAdaptor() override = default;
|
||||
|
||||
public slots:
|
||||
void Close();
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_SESSIONADAPTOR_H
|
241
src/fdosecrets/widgets/AccessControlDialog.cpp
Normal file
241
src/fdosecrets/widgets/AccessControlDialog.cpp
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 "AccessControlDialog.h"
|
||||
#include "ui_AccessControlDialog.h"
|
||||
|
||||
#include "fdosecrets/widgets/RowButtonHelper.h"
|
||||
|
||||
#include "core/Entry.h"
|
||||
|
||||
#include <QWindow>
|
||||
|
||||
#include <utility>
|
||||
|
||||
AccessControlDialog::AccessControlDialog(QWindow* parent,
|
||||
const QList<Entry*>& entries,
|
||||
const QString& app,
|
||||
AuthOptions authOptions)
|
||||
: m_ui(new Ui::AccessControlDialog())
|
||||
, m_model(new EntryModel(entries))
|
||||
{
|
||||
if (parent) {
|
||||
// Force the creation of the QWindow, without this windowHandle() will return nullptr
|
||||
winId();
|
||||
auto window = windowHandle();
|
||||
Q_ASSERT(window);
|
||||
window->setTransientParent(parent);
|
||||
}
|
||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->cancelButton, &QPushButton::clicked, this, [this]() { done(DenyAll); });
|
||||
connect(m_ui->allowButton, &QPushButton::clicked, this, [this]() { done(AllowSelected); });
|
||||
connect(m_ui->itemsTable, &QTableView::clicked, m_model.data(), &EntryModel::toggleCheckState);
|
||||
connect(m_ui->rememberCheck, &QCheckBox::clicked, this, &AccessControlDialog::rememberChecked);
|
||||
connect(this, &QDialog::finished, this, &AccessControlDialog::dialogFinished);
|
||||
|
||||
m_ui->rememberMsg->setCloseButtonVisible(false);
|
||||
m_ui->rememberMsg->setMessageType(MessageWidget::Information);
|
||||
|
||||
m_ui->appLabel->setText(m_ui->appLabel->text().arg(app));
|
||||
|
||||
m_ui->itemsTable->setModel(m_model.data());
|
||||
installWidgetItemDelegate<DenyButton>(m_ui->itemsTable, 2, [this](QWidget* p, const QModelIndex& idx) {
|
||||
auto btn = new DenyButton(p, idx);
|
||||
connect(btn, &DenyButton::clicked, this, &AccessControlDialog::denyEntryClicked);
|
||||
return btn;
|
||||
});
|
||||
m_ui->itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
m_ui->itemsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
m_ui->itemsTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
m_ui->itemsTable->resizeColumnsToContents();
|
||||
|
||||
if (!authOptions.testFlag(AuthOption::Remember)) {
|
||||
m_ui->rememberCheck->setHidden(true);
|
||||
m_ui->rememberCheck->setChecked(false);
|
||||
}
|
||||
if (!authOptions.testFlag(AuthOption::PerEntryDeny)) {
|
||||
m_ui->itemsTable->horizontalHeader()->setSectionHidden(2, true);
|
||||
}
|
||||
|
||||
m_ui->allowButton->setFocus();
|
||||
}
|
||||
|
||||
AccessControlDialog::~AccessControlDialog() = default;
|
||||
|
||||
void AccessControlDialog::rememberChecked(bool checked)
|
||||
{
|
||||
if (checked) {
|
||||
m_ui->rememberMsg->animatedShow();
|
||||
} else {
|
||||
m_ui->rememberMsg->animatedHide();
|
||||
}
|
||||
}
|
||||
|
||||
void AccessControlDialog::denyEntryClicked(Entry* entry, const QModelIndex& index)
|
||||
{
|
||||
m_decisions.insert(entry, AuthDecision::Denied);
|
||||
m_model->removeRow(index.row());
|
||||
if (m_model->rowCount({}) == 0) {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
|
||||
void AccessControlDialog::dialogFinished(int result)
|
||||
{
|
||||
auto allow = m_ui->rememberCheck->isChecked() ? AuthDecision::Allowed : AuthDecision::AllowedOnce;
|
||||
auto deny = m_ui->rememberCheck->isChecked() ? AuthDecision::Denied : AuthDecision::DeniedOnce;
|
||||
|
||||
for (int row = 0; row != m_model->rowCount({}); ++row) {
|
||||
auto entry = m_model->data(m_model->index(row, 2), Qt::EditRole).value<Entry*>();
|
||||
auto selected = m_model->data(m_model->index(row, 0), Qt::CheckStateRole).value<Qt::CheckState>();
|
||||
Q_ASSERT(entry);
|
||||
switch (result) {
|
||||
case AllowSelected:
|
||||
if (selected) {
|
||||
m_decisions.insert(entry, allow);
|
||||
} else {
|
||||
m_decisions.insert(entry, AuthDecision::Undecided);
|
||||
}
|
||||
break;
|
||||
case DenyAll:
|
||||
m_decisions.insert(entry, deny);
|
||||
break;
|
||||
case Rejected:
|
||||
default:
|
||||
m_decisions.insert(entry, AuthDecision::Undecided);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit finished(m_decisions);
|
||||
}
|
||||
|
||||
QHash<Entry*, AuthDecision> AccessControlDialog::decisions() const
|
||||
{
|
||||
return m_decisions;
|
||||
}
|
||||
|
||||
AccessControlDialog::EntryModel::EntryModel(QList<Entry*> entries, QObject* parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_entries(std::move(entries))
|
||||
, m_selected(QSet<Entry*>::fromList(m_entries))
|
||||
{
|
||||
}
|
||||
|
||||
int AccessControlDialog::EntryModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return isValid(parent) ? 0 : m_entries.count();
|
||||
}
|
||||
|
||||
int AccessControlDialog::EntryModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return isValid(parent) ? 0 : 3;
|
||||
}
|
||||
|
||||
bool AccessControlDialog::EntryModel::isValid(const QModelIndex& index) const
|
||||
{
|
||||
return index.isValid() && index.row() < rowCount({}) && index.column() < columnCount({});
|
||||
}
|
||||
|
||||
void AccessControlDialog::EntryModel::toggleCheckState(const QModelIndex& index)
|
||||
{
|
||||
if (!isValid(index)) {
|
||||
return;
|
||||
}
|
||||
auto entry = m_entries.at(index.row());
|
||||
// click anywhere in the row to check/uncheck the item
|
||||
auto it = m_selected.find(entry);
|
||||
if (it == m_selected.end()) {
|
||||
m_selected.insert(entry);
|
||||
} else {
|
||||
m_selected.erase(it);
|
||||
}
|
||||
auto rowIdx = index.sibling(index.row(), 0);
|
||||
emit dataChanged(rowIdx, rowIdx, {Qt::CheckStateRole});
|
||||
}
|
||||
|
||||
QVariant AccessControlDialog::EntryModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!isValid(index)) {
|
||||
return {};
|
||||
}
|
||||
auto entry = m_entries.at(index.row());
|
||||
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return entry->title();
|
||||
case Qt::DecorationRole:
|
||||
return entry->icon();
|
||||
case Qt::CheckStateRole:
|
||||
return QVariant::fromValue(m_selected.contains(entry) ? Qt::Checked : Qt::Unchecked);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case 1:
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return entry->username();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case 2:
|
||||
switch (role) {
|
||||
case Qt::EditRole:
|
||||
return QVariant::fromValue(entry);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool AccessControlDialog::EntryModel::removeRows(int row, int count, const QModelIndex& parent)
|
||||
{
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
while (count--) {
|
||||
m_entries.removeAt(row);
|
||||
}
|
||||
endRemoveRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
AccessControlDialog::DenyButton::DenyButton(QWidget* p, const QModelIndex& idx)
|
||||
: QPushButton(p)
|
||||
, m_index(idx)
|
||||
, m_entry()
|
||||
{
|
||||
setText(tr("Deny for this program"));
|
||||
connect(this, &QPushButton::clicked, [this]() { emit clicked(entry(), m_index); });
|
||||
}
|
||||
|
||||
void AccessControlDialog::DenyButton::setEntry(Entry* e)
|
||||
{
|
||||
m_entry = e;
|
||||
}
|
||||
|
||||
Entry* AccessControlDialog::DenyButton::entry() const
|
||||
{
|
||||
return m_entry;
|
||||
}
|
125
src/fdosecrets/widgets/AccessControlDialog.h
Normal file
125
src/fdosecrets/widgets/AccessControlDialog.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 KEEPASSXC_FDOSECRETS_ACCESSCONTROLDIALOG_H
|
||||
#define KEEPASSXC_FDOSECRETS_ACCESSCONTROLDIALOG_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QDialog>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QScopedPointer>
|
||||
#include <QSet>
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
class Entry;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class AccessControlDialog;
|
||||
}
|
||||
|
||||
enum class AuthOption
|
||||
{
|
||||
None = 0,
|
||||
Remember = 1 << 1,
|
||||
PerEntryDeny = 1 << 2,
|
||||
};
|
||||
Q_DECLARE_FLAGS(AuthOptions, AuthOption);
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(AuthOptions);
|
||||
|
||||
class AccessControlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AccessControlDialog(QWindow* parent,
|
||||
const QList<Entry*>& entries,
|
||||
const QString& app,
|
||||
AuthOptions authOptions = AuthOption::Remember | AuthOption::PerEntryDeny);
|
||||
~AccessControlDialog() override;
|
||||
|
||||
enum DialogCode
|
||||
{
|
||||
Rejected,
|
||||
AllowSelected,
|
||||
DenyAll,
|
||||
};
|
||||
|
||||
QHash<Entry*, AuthDecision> decisions() const;
|
||||
|
||||
signals:
|
||||
void finished(const QHash<Entry*, AuthDecision>& results);
|
||||
|
||||
private slots:
|
||||
void rememberChecked(bool checked);
|
||||
void denyEntryClicked(Entry* entry, const QModelIndex& index);
|
||||
void dialogFinished(int result);
|
||||
|
||||
private:
|
||||
class EntryModel;
|
||||
class DenyButton;
|
||||
|
||||
QScopedPointer<Ui::AccessControlDialog> m_ui;
|
||||
QScopedPointer<EntryModel> m_model;
|
||||
QHash<Entry*, AuthDecision> m_decisions;
|
||||
};
|
||||
|
||||
class AccessControlDialog::EntryModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EntryModel(QList<Entry*> entries, QObject* parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex& parent) const override;
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
bool removeRows(int row, int count, const QModelIndex& parent) override;
|
||||
|
||||
public slots:
|
||||
void toggleCheckState(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
bool isValid(const QModelIndex& index) const;
|
||||
|
||||
QList<Entry*> m_entries;
|
||||
QSet<Entry*> m_selected;
|
||||
};
|
||||
|
||||
class AccessControlDialog::DenyButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(Entry* entry READ entry WRITE setEntry USER true)
|
||||
|
||||
QPersistentModelIndex m_index;
|
||||
QPointer<Entry> m_entry;
|
||||
|
||||
public:
|
||||
explicit DenyButton(QWidget* p, const QModelIndex& idx);
|
||||
|
||||
void setEntry(Entry* e);
|
||||
Entry* entry() const;
|
||||
|
||||
signals:
|
||||
void clicked(Entry*, const QModelIndex& idx);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_ACCESSCONTROLDIALOG_H
|
133
src/fdosecrets/widgets/AccessControlDialog.ui
Normal file
133
src/fdosecrets/widgets/AccessControlDialog.ui
Normal file
@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AccessControlDialog</class>
|
||||
<widget class="QDialog" name="AccessControlDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>405</width>
|
||||
<height>252</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>KeePassXC - Access Request</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="appLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">%1 </span>is requesting access to the following entries:</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="itemsTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="MessageWidget" name="rememberMsg" native="true">
|
||||
<property name="text" stdset="0">
|
||||
<string>Your decision for above entries will be remembered for the duration the requesting client is running.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rememberCheck">
|
||||
<property name="text">
|
||||
<string>Remember</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="allowButton">
|
||||
<property name="accessibleName">
|
||||
<string>Allow access to entries</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Allow Selected</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Deny All</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
68
src/fdosecrets/widgets/RowButtonHelper.cpp
Normal file
68
src/fdosecrets/widgets/RowButtonHelper.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "RowButtonHelper.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QItemEditorFactory>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
class WidgetItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
std::function<QWidget*(QWidget*, const QModelIndex&)> m_create;
|
||||
|
||||
public:
|
||||
explicit WidgetItemDelegate(QObject* parent, std::function<QWidget*(QWidget*, const QModelIndex&)>&& create)
|
||||
: QStyledItemDelegate(parent)
|
||||
, m_create(std::move(create))
|
||||
{
|
||||
}
|
||||
|
||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex& index) const override
|
||||
{
|
||||
if (!index.isValid())
|
||||
return nullptr;
|
||||
return m_create(parent, index);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void installWidgetItemDelegate(QAbstractItemView* view,
|
||||
int column,
|
||||
std::function<QWidget*(QWidget*, const QModelIndex&)>&& create)
|
||||
{
|
||||
auto delegate = new WidgetItemDelegate(view, std::move(create));
|
||||
// doesn't take ownership
|
||||
view->setItemDelegateForColumn(column, delegate);
|
||||
|
||||
for (int row = 0; row != view->model()->rowCount({}); ++row) {
|
||||
view->openPersistentEditor(view->model()->index(row, column));
|
||||
}
|
||||
QObject::connect(view->model(),
|
||||
&QAbstractItemModel::rowsInserted,
|
||||
delegate,
|
||||
[view, column](const QModelIndex&, int first, int last) {
|
||||
for (int i = first; i <= last; ++i) {
|
||||
auto idx = view->model()->index(i, column);
|
||||
view->openPersistentEditor(idx);
|
||||
}
|
||||
});
|
||||
}
|
42
src/fdosecrets/widgets/RowButtonHelper.h
Normal file
42
src/fdosecrets/widgets/RowButtonHelper.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETS_ROWBUTTONHELPER_H
|
||||
#define KEEPASSXC_FDOSECRETS_ROWBUTTONHELPER_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
class QAbstractItemView;
|
||||
class QWidget;
|
||||
class QModelIndex;
|
||||
|
||||
void installWidgetItemDelegate(QAbstractItemView* view,
|
||||
int column,
|
||||
std::function<QWidget*(QWidget*, const QModelIndex&)>&& create);
|
||||
|
||||
/**
|
||||
* @brief Open an editor on the cell, the editor's user property will be edited.
|
||||
*/
|
||||
template <typename Editor>
|
||||
void installWidgetItemDelegate(QAbstractItemView* view,
|
||||
int column,
|
||||
std::function<Editor*(QWidget*, const QModelIndex&)>&& create)
|
||||
{
|
||||
installWidgetItemDelegate(view, column, [create](QWidget* p, const QModelIndex& idx) { return create(p, idx); });
|
||||
}
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_ROWBUTTONHELPER_H
|
@ -19,11 +19,10 @@
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/DatabaseIcons.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/Icons.h"
|
||||
@ -244,35 +243,22 @@ namespace FdoSecrets
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSessionModel::SettingsSessionModel(FdoSecretsPlugin* plugin, QObject* parent)
|
||||
SettingsClientModel::SettingsClientModel(DBusMgr& dbus, QObject* parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_service(nullptr)
|
||||
, m_dbus(dbus)
|
||||
{
|
||||
setService(plugin->serviceInstance());
|
||||
connect(plugin, &FdoSecretsPlugin::secretServiceStarted, this, [plugin, this]() {
|
||||
setService(plugin->serviceInstance());
|
||||
});
|
||||
connect(plugin, &FdoSecretsPlugin::secretServiceStopped, this, [this]() { setService(nullptr); });
|
||||
populateModel();
|
||||
}
|
||||
|
||||
void SettingsSessionModel::setService(Service* service)
|
||||
{
|
||||
auto old = m_service;
|
||||
m_service = service;
|
||||
if (old != m_service) {
|
||||
populateModel();
|
||||
}
|
||||
}
|
||||
|
||||
int SettingsSessionModel::rowCount(const QModelIndex& parent) const
|
||||
int SettingsClientModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return m_sessions.size();
|
||||
return m_clients.size();
|
||||
}
|
||||
|
||||
int SettingsSessionModel::columnCount(const QModelIndex& parent) const
|
||||
int SettingsClientModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
@ -280,7 +266,7 @@ namespace FdoSecrets
|
||||
return 2;
|
||||
}
|
||||
|
||||
QVariant SettingsSessionModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
QVariant SettingsClientModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal) {
|
||||
return {};
|
||||
@ -300,89 +286,88 @@ namespace FdoSecrets
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsSessionModel::data(const QModelIndex& index, int role) const
|
||||
QVariant SettingsClientModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
const auto& sess = m_sessions[index.row()];
|
||||
if (!sess) {
|
||||
const auto& client = m_clients[index.row()];
|
||||
if (!client) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return dataForApplication(sess, role);
|
||||
return dataForApplication(client, role);
|
||||
case 1:
|
||||
return dataForManage(sess, role);
|
||||
return dataForManage(client, role);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsSessionModel::dataForApplication(Session* sess, int role) const
|
||||
QVariant SettingsClientModel::dataForApplication(const DBusClientPtr& client, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return sess->peer();
|
||||
return client->name();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsSessionModel::dataForManage(Session* sess, int role) const
|
||||
QVariant SettingsClientModel::dataForManage(const DBusClientPtr& client, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::EditRole: {
|
||||
return QVariant::fromValue(sess);
|
||||
return QVariant::fromValue(client);
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSessionModel::populateModel()
|
||||
void SettingsClientModel::populateModel()
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
m_sessions.clear();
|
||||
m_clients.clear();
|
||||
|
||||
if (m_service) {
|
||||
// Add existing database tabs
|
||||
for (const auto& sess : m_service->sessions()) {
|
||||
sessionAdded(sess, false);
|
||||
}
|
||||
|
||||
// connect signals
|
||||
connect(m_service, &Service::sessionOpened, this, [this](Session* sess) { sessionAdded(sess, true); });
|
||||
connect(m_service, &Service::sessionClosed, this, &SettingsSessionModel::sessionRemoved);
|
||||
// Add existing database tabs
|
||||
for (const auto& client : m_dbus.clients()) {
|
||||
clientConnected(client, false);
|
||||
}
|
||||
|
||||
// connect signals
|
||||
connect(&m_dbus, &DBusMgr::clientConnected, this, [this](const DBusClientPtr& client) {
|
||||
clientConnected(client, true);
|
||||
});
|
||||
connect(&m_dbus, &DBusMgr::clientDisconnected, this, &SettingsClientModel::clientDisconnected);
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void SettingsSessionModel::sessionAdded(Session* sess, bool emitSignals)
|
||||
void SettingsClientModel::clientConnected(const DBusClientPtr& client, bool emitSignals)
|
||||
{
|
||||
int row = m_sessions.size();
|
||||
int row = m_clients.size();
|
||||
if (emitSignals) {
|
||||
beginInsertRows({}, row, row);
|
||||
}
|
||||
|
||||
m_sessions.append(sess);
|
||||
m_clients.append(client);
|
||||
|
||||
if (emitSignals) {
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSessionModel::sessionRemoved(Session* sess)
|
||||
void SettingsClientModel::clientDisconnected(const DBusClientPtr& client)
|
||||
{
|
||||
for (int i = 0; i != m_sessions.size(); i++) {
|
||||
if (m_sessions[i] == sess) {
|
||||
for (int i = 0; i != m_clients.size(); i++) {
|
||||
if (m_clients[i] == client) {
|
||||
beginRemoveRows({}, i, i);
|
||||
|
||||
m_sessions[i]->disconnect(this);
|
||||
m_sessions.removeAt(i);
|
||||
m_clients.removeAt(i);
|
||||
|
||||
endRemoveRows();
|
||||
break;
|
||||
|
@ -18,12 +18,13 @@
|
||||
#ifndef KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
|
||||
#define KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
|
||||
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QPointer>
|
||||
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
class FdoSecretsPlugin;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
@ -58,14 +59,13 @@ namespace FdoSecrets
|
||||
QList<QPointer<DatabaseWidget>> m_dbs;
|
||||
};
|
||||
|
||||
class Service;
|
||||
class Session;
|
||||
class DBusMgr;
|
||||
|
||||
class SettingsSessionModel : public QAbstractTableModel
|
||||
class SettingsClientModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsSessionModel(FdoSecretsPlugin* plugin, QObject* parent = nullptr);
|
||||
explicit SettingsClientModel(DBusMgr& dbus, QObject* parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex& parent) const override;
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
@ -73,22 +73,20 @@ namespace FdoSecrets
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
private:
|
||||
void setService(Service* service);
|
||||
|
||||
QVariant dataForApplication(Session* sess, int role) const;
|
||||
QVariant dataForManage(Session* sess, int role) const;
|
||||
QVariant dataForApplication(const DBusClientPtr& client, int role) const;
|
||||
QVariant dataForManage(const DBusClientPtr& client, int role) const;
|
||||
|
||||
private slots:
|
||||
void populateModel();
|
||||
void sessionAdded(Session* sess, bool emitSignals);
|
||||
void sessionRemoved(Session* sess);
|
||||
void clientConnected(const DBusClientPtr& client, bool emitSignals);
|
||||
void clientDisconnected(const DBusClientPtr& client);
|
||||
|
||||
private:
|
||||
// source
|
||||
QPointer<Service> m_service;
|
||||
DBusMgr& m_dbus;
|
||||
|
||||
// internal copy, so we can emit with changed index
|
||||
QList<Session*> m_sessions;
|
||||
QList<DBusClientPtr> m_clients;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
@ -20,24 +20,21 @@
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
#include "fdosecrets/widgets/RowButtonHelper.h"
|
||||
#include "fdosecrets/widgets/SettingsModels.h"
|
||||
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/Icons.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QHeaderView>
|
||||
#include <QItemEditorFactory>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QToolBar>
|
||||
#include <QVariant>
|
||||
|
||||
using FdoSecrets::Session;
|
||||
using FdoSecrets::DBusClientPtr;
|
||||
using FdoSecrets::SettingsClientModel;
|
||||
using FdoSecrets::SettingsDatabaseModel;
|
||||
using FdoSecrets::SettingsSessionModel;
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -158,10 +155,10 @@ namespace
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(Session* session READ session WRITE setSession USER true)
|
||||
Q_PROPERTY(const DBusClientPtr& client READ client WRITE setClient USER true)
|
||||
|
||||
public:
|
||||
explicit ManageSession(FdoSecretsPlugin*, QWidget* parent = nullptr)
|
||||
explicit ManageSession(QWidget* parent = nullptr)
|
||||
: QToolBar(parent)
|
||||
{
|
||||
setFloatable(false);
|
||||
@ -173,15 +170,15 @@ namespace
|
||||
spacer->setVisible(true);
|
||||
addWidget(spacer);
|
||||
|
||||
m_disconnectAct = new QAction(tr("Disconnect"), this);
|
||||
m_disconnectAct->setIcon(icons()->icon(QStringLiteral("dialog-close")));
|
||||
m_disconnectAct->setToolTip(tr("Disconnect this application"));
|
||||
connect(m_disconnectAct, &QAction::triggered, this, [this]() {
|
||||
if (m_session) {
|
||||
m_session->close();
|
||||
auto disconnectAct = new QAction(tr("Disconnect"), this);
|
||||
disconnectAct->setIcon(icons()->icon(QStringLiteral("dialog-close")));
|
||||
disconnectAct->setToolTip(tr("Disconnect this application"));
|
||||
connect(disconnectAct, &QAction::triggered, this, [this]() {
|
||||
if (m_client) {
|
||||
m_client->disconnectDBus();
|
||||
}
|
||||
});
|
||||
addAction(m_disconnectAct);
|
||||
addAction(disconnectAct);
|
||||
|
||||
// use a dummy widget to center the buttons
|
||||
spacer = new QWidget(this);
|
||||
@ -190,71 +187,46 @@ namespace
|
||||
addWidget(spacer);
|
||||
}
|
||||
|
||||
Session* session()
|
||||
const DBusClientPtr& client() const
|
||||
{
|
||||
return m_session;
|
||||
return m_client;
|
||||
}
|
||||
|
||||
void setSession(Session* sess)
|
||||
void setClient(DBusClientPtr client)
|
||||
{
|
||||
m_session = sess;
|
||||
m_client = std::move(client);
|
||||
}
|
||||
|
||||
private:
|
||||
Session* m_session = nullptr;
|
||||
QAction* m_disconnectAct = nullptr;
|
||||
};
|
||||
|
||||
template <typename T> class Creator : public QItemEditorCreatorBase
|
||||
{
|
||||
public:
|
||||
inline explicit Creator(FdoSecretsPlugin* plugin)
|
||||
: QItemEditorCreatorBase()
|
||||
, m_plugin(plugin)
|
||||
, m_propertyName(T::staticMetaObject.userProperty().name())
|
||||
{
|
||||
}
|
||||
|
||||
inline QWidget* createWidget(QWidget* parent) const override
|
||||
{
|
||||
return new T(m_plugin, parent);
|
||||
}
|
||||
|
||||
inline QByteArray valuePropertyName() const override
|
||||
{
|
||||
return m_propertyName;
|
||||
}
|
||||
|
||||
private:
|
||||
FdoSecretsPlugin* m_plugin;
|
||||
QByteArray m_propertyName;
|
||||
DBusClientPtr m_client{};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::SettingsWidgetFdoSecrets())
|
||||
, m_factory(new QItemEditorFactory)
|
||||
, m_plugin(plugin)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->warningMsg->setHidden(true);
|
||||
m_ui->warningMsg->setCloseButtonVisible(false);
|
||||
|
||||
auto sessModel = new SettingsSessionModel(plugin, this);
|
||||
m_ui->tableSessions->setModel(sessModel);
|
||||
setupView(m_ui->tableSessions, 1, qMetaTypeId<Session*>(), new Creator<ManageSession>(m_plugin));
|
||||
auto clientModel = new SettingsClientModel(*plugin->dbus(), this);
|
||||
m_ui->tableClients->setModel(clientModel);
|
||||
installWidgetItemDelegate<ManageSession>(
|
||||
m_ui->tableClients, 1, [](QWidget* p, const QModelIndex&) { return new ManageSession(p); });
|
||||
|
||||
// config header after setting model, otherwise the header doesn't have enough sections
|
||||
auto sessViewHeader = m_ui->tableSessions->horizontalHeader();
|
||||
sessViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
sessViewHeader->setSectionsClickable(false);
|
||||
sessViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
|
||||
sessViewHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
|
||||
auto clientViewHeader = m_ui->tableClients->horizontalHeader();
|
||||
clientViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
clientViewHeader->setSectionsClickable(false);
|
||||
clientViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
|
||||
clientViewHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
|
||||
|
||||
auto dbModel = new SettingsDatabaseModel(plugin->dbTabs(), this);
|
||||
m_ui->tableDatabases->setModel(dbModel);
|
||||
setupView(m_ui->tableDatabases, 2, qMetaTypeId<DatabaseWidget*>(), new Creator<ManageDatabase>(m_plugin));
|
||||
installWidgetItemDelegate<ManageDatabase>(
|
||||
m_ui->tableDatabases, 2, [plugin](QWidget* p, const QModelIndex&) { return new ManageDatabase(plugin, p); });
|
||||
|
||||
// config header after setting model, otherwise the header doesn't have enough sections
|
||||
auto dbViewHeader = m_ui->tableDatabases->horizontalHeader();
|
||||
@ -277,40 +249,22 @@ SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWi
|
||||
connect(m_plugin, SIGNAL(secretServiceStopped()), &m_checkTimer, SLOT(start()));
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::setupView(QAbstractItemView* view,
|
||||
int manageColumn,
|
||||
int editorTypeId,
|
||||
QItemEditorCreatorBase* creator)
|
||||
{
|
||||
auto manageButtonDelegate = new QStyledItemDelegate(this);
|
||||
m_factory->registerEditor(editorTypeId, creator);
|
||||
manageButtonDelegate->setItemEditorFactory(m_factory.data());
|
||||
view->setItemDelegateForColumn(manageColumn, manageButtonDelegate);
|
||||
connect(view->model(),
|
||||
&QAbstractItemModel::rowsInserted,
|
||||
this,
|
||||
[view, manageColumn](const QModelIndex&, int first, int last) {
|
||||
for (int i = first; i <= last; ++i) {
|
||||
auto idx = view->model()->index(i, manageColumn);
|
||||
view->openPersistentEditor(idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SettingsWidgetFdoSecrets::~SettingsWidgetFdoSecrets() = default;
|
||||
|
||||
void SettingsWidgetFdoSecrets::loadSettings()
|
||||
{
|
||||
m_ui->enableFdoSecretService->setChecked(FdoSecrets::settings()->isEnabled());
|
||||
m_ui->showNotification->setChecked(FdoSecrets::settings()->showNotification());
|
||||
m_ui->noConfirmDeleteItem->setChecked(FdoSecrets::settings()->noConfirmDeleteItem());
|
||||
m_ui->confirmDeleteItem->setChecked(FdoSecrets::settings()->confirmDeleteItem());
|
||||
m_ui->confirmAccessItem->setChecked(FdoSecrets::settings()->confirmAccessItem());
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::saveSettings()
|
||||
{
|
||||
FdoSecrets::settings()->setEnabled(m_ui->enableFdoSecretService->isChecked());
|
||||
FdoSecrets::settings()->setShowNotification(m_ui->showNotification->isChecked());
|
||||
FdoSecrets::settings()->setNoConfirmDeleteItem(m_ui->noConfirmDeleteItem->isChecked());
|
||||
FdoSecrets::settings()->setConfirmDeleteItem(m_ui->confirmDeleteItem->isChecked());
|
||||
FdoSecrets::settings()->setConfirmAccessItem(m_ui->confirmAccessItem->isChecked());
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::showEvent(QShowEvent* event)
|
||||
@ -333,17 +287,9 @@ void SettingsWidgetFdoSecrets::checkDBusName()
|
||||
return;
|
||||
}
|
||||
|
||||
auto reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral(DBUS_SERVICE_SECRET));
|
||||
if (!reply.isValid()) {
|
||||
if (m_plugin->dbus()->serviceOccupied()) {
|
||||
m_ui->warningMsg->showMessage(
|
||||
tr("<b>Error:</b> Failed to connect to DBus. Please check your DBus setup."), MessageWidget::Error, -1);
|
||||
m_ui->enableFdoSecretService->setChecked(false);
|
||||
m_ui->enableFdoSecretService->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (reply.value()) {
|
||||
m_ui->warningMsg->showMessage(
|
||||
tr("<b>Warning:</b> ") + m_plugin->reportExistingService(), MessageWidget::Warning, -1);
|
||||
tr("<b>Warning:</b> ") + m_plugin->dbus()->reportExistingService(), MessageWidget::Warning, -1);
|
||||
m_ui->enableFdoSecretService->setChecked(false);
|
||||
m_ui->enableFdoSecretService->setEnabled(false);
|
||||
return;
|
||||
|
@ -25,16 +25,6 @@
|
||||
#include <QWidget>
|
||||
|
||||
class QAbstractItemView;
|
||||
class QItemEditorCreatorBase;
|
||||
class QItemEditorFactory;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Session;
|
||||
class Collection;
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
class FdoSecretsPlugin;
|
||||
|
||||
@ -61,12 +51,8 @@ protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
private:
|
||||
void setupView(QAbstractItemView* view, int manageColumn, int editorTypeId, QItemEditorCreatorBase* creator);
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::SettingsWidgetFdoSecrets> m_ui;
|
||||
QScopedPointer<QItemEditorFactory> m_factory;
|
||||
FdoSecretsPlugin* m_plugin;
|
||||
QTimer m_checkTimer;
|
||||
};
|
||||
|
@ -49,17 +49,36 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showNotification">
|
||||
<property name="text">
|
||||
<string>Show notification when credentials are requested</string>
|
||||
<string>Show notification when passwords are retrieved by clients</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="noConfirmDeleteItem">
|
||||
<widget class="QCheckBox" name="confirmAccessItem">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If recycle bin is enabled for the database, entries will be moved to recycle bin directly. Otherwise, they will be deleted without confirmation.</p><p>You will still be prompted if any entries are referenced by others.</p></body></html></string>
|
||||
<string><html><head/><body><p>If enabled, any attempt to read a password must be confirmed. Otherwise, clients can read passwords without confirmation when the database is unlocked.</p><p>This option only covers the access to the password of an entry. Clients can always enumerate the items of exposed databases and query their attributes.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Don't confirm when entries are deleted by clients</string>
|
||||
<string>Confirm when passwords are retrieved by clients</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="confirmDeleteItem">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-family:'-apple-system','BlinkMacSystemFont','Segoe UI','Helvetica','Arial','sans-serif','Apple Color Emoji','Segoe UI Emoji'; font-size:14px; color:#24292e; background-color:#ffffff;">This setting does not override disabling recycle bin prompts</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Confirm when clients request entry deletion</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -120,7 +139,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="tableSessions">
|
||||
<widget class="QTableView" name="tableClients">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
|
@ -474,20 +474,20 @@ void DatabaseWidget::deleteSelectedEntries()
|
||||
deleteEntries(std::move(selectedEntries));
|
||||
}
|
||||
|
||||
void DatabaseWidget::deleteEntries(QList<Entry*> selectedEntries)
|
||||
void DatabaseWidget::deleteEntries(QList<Entry*> selectedEntries, bool confirm)
|
||||
{
|
||||
// Confirm entry removal before moving forward
|
||||
auto* recycleBin = m_db->metadata()->recycleBin();
|
||||
bool permanent = (recycleBin && recycleBin->findEntryByUuid(selectedEntries.first()->uuid()))
|
||||
|| !m_db->metadata()->recycleBinEnabled();
|
||||
|
||||
if (!confirmDeleteEntries(selectedEntries, permanent)) {
|
||||
if (confirm && !confirmDeleteEntries(selectedEntries, permanent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find references to selected entries and prompt for direction if necessary
|
||||
auto it = selectedEntries.begin();
|
||||
while (it != selectedEntries.end()) {
|
||||
while (confirm && it != selectedEntries.end()) {
|
||||
auto references = m_db->rootGroup()->referencesRecursive(*it);
|
||||
if (!references.isEmpty()) {
|
||||
// Ignore references that are selected for deletion
|
||||
|
@ -162,7 +162,7 @@ public slots:
|
||||
void createEntry();
|
||||
void cloneEntry();
|
||||
void deleteSelectedEntries();
|
||||
void deleteEntries(QList<Entry*> entries);
|
||||
void deleteEntries(QList<Entry*> entries, bool confirm = true);
|
||||
void focusOnEntries(bool editIfFocused = false);
|
||||
void focusOnGroups(bool editIfFocused = false);
|
||||
void moveEntryUp();
|
||||
|
@ -20,13 +20,13 @@
|
||||
#include "TestGlobal.h"
|
||||
|
||||
#include "core/EntrySearcher.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "fdosecrets/GcryptMPI.h"
|
||||
#include "fdosecrets/dbus/DBusMgr.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestFdoSecrets)
|
||||
|
||||
void TestFdoSecrets::initTestCase()
|
||||
@ -144,3 +144,39 @@ void TestFdoSecrets::testSpecialCharsInAttributeValue()
|
||||
QCOMPARE(res[0]->title(), QStringLiteral("titleB"));
|
||||
}
|
||||
}
|
||||
|
||||
void TestFdoSecrets::testDBusPathParse()
|
||||
{
|
||||
using FdoSecrets::DBusMgr;
|
||||
using PathType = FdoSecrets::DBusMgr::PathType;
|
||||
|
||||
auto parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/secrets"));
|
||||
QCOMPARE(parsed.type, PathType::Service);
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/secrets/collection/xxx"));
|
||||
QCOMPARE(parsed.type, PathType::Collection);
|
||||
QCOMPARE(parsed.id, QStringLiteral("xxx"));
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/secrets/collection/xxx/yyy"));
|
||||
QCOMPARE(parsed.type, PathType::Item);
|
||||
QCOMPARE(parsed.id, QStringLiteral("yyy"));
|
||||
QCOMPARE(parsed.parentId, QStringLiteral("xxx"));
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/secrets/aliases/xxx"));
|
||||
QCOMPARE(parsed.type, PathType::Aliases);
|
||||
QCOMPARE(parsed.id, QStringLiteral("xxx"));
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/secrets/session/xxx"));
|
||||
QCOMPARE(parsed.type, PathType::Session);
|
||||
QCOMPARE(parsed.id, QStringLiteral("xxx"));
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/secrets/prompt/xxx"));
|
||||
QCOMPARE(parsed.type, PathType::Prompt);
|
||||
QCOMPARE(parsed.id, QStringLiteral("xxx"));
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org/freedesktop/other/prompt/xxx"));
|
||||
QCOMPARE(parsed.type, PathType::Unknown);
|
||||
|
||||
parsed = DBusMgr::parsePath(QStringLiteral("/org"));
|
||||
QCOMPARE(parsed.type, PathType::Unknown);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ private slots:
|
||||
void testDhIetf1024Sha256Aes128CbcPkcs7();
|
||||
void testCrazyAttributeKey();
|
||||
void testSpecialCharsInAttributeValue();
|
||||
void testDBusPathParse();
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_TESTFDOSECRETS_H
|
||||
|
Binary file not shown.
@ -1,33 +0,0 @@
|
||||
<interface name="org.freedesktop.Secret.Collection">
|
||||
<property name="Items" type="ao" access="read"/>
|
||||
<property name="Label" type="s" access="readwrite"/>
|
||||
<property name="Locked" type="b" access="read"/>
|
||||
<property name="Created" type="t" access="read"/>
|
||||
<property name="Modified" type="t" access="read"/>
|
||||
<signal name="ItemCreated">
|
||||
<arg name="item" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ItemDeleted">
|
||||
<arg name="item" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ItemChanged">
|
||||
<arg name="item" type="o" direction="out"/>
|
||||
</signal>
|
||||
<method name="Delete">
|
||||
<arg type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="SearchItems">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="attributes" type="a{ss}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="StringStringMap"/>
|
||||
</method>
|
||||
<method name="CreateItem">
|
||||
<arg type="o" direction="out"/>
|
||||
<arg name="properties" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||
<arg name="secret" type="(oayays)" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="FdoSecrets::SecretStruct"/>
|
||||
<arg name="replace" type="b" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
@ -1,21 +0,0 @@
|
||||
<interface name="org.freedesktop.Secret.Item">
|
||||
<property name="Locked" type="b" access="read"/>
|
||||
<property name="Attributes" type="a{ss}" access="readwrite">
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="StringStringMap"/>
|
||||
</property>
|
||||
<property name="Label" type="s" access="readwrite"/>
|
||||
<property name="Created" type="t" access="read"/>
|
||||
<property name="Modified" type="t" access="read"/>
|
||||
<method name="Delete">
|
||||
<arg type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="GetSecret">
|
||||
<arg type="(oayays)" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="FdoSecrets::SecretStruct"/>
|
||||
<arg name="session" type="o" direction="in"/>
|
||||
</method>
|
||||
<method name="SetSecret">
|
||||
<arg name="secret" type="(oayays)" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="FdoSecrets::SecretStruct"/>
|
||||
</method>
|
||||
</interface>
|
@ -1,11 +0,0 @@
|
||||
<interface name="org.freedesktop.Secret.Prompt">
|
||||
<signal name="Completed">
|
||||
<arg name="dismissed" type="b" direction="out"/>
|
||||
<arg name="result" type="v" direction="out"/>
|
||||
</signal>
|
||||
<method name="Prompt">
|
||||
<arg name="windowId" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="Dismiss">
|
||||
</method>
|
||||
</interface>
|
@ -1,55 +0,0 @@
|
||||
<interface name="org.freedesktop.Secret.Service">
|
||||
<property name="Collections" type="ao" access="read"/>
|
||||
<signal name="CollectionCreated">
|
||||
<arg name="collection" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="CollectionDeleted">
|
||||
<arg name="collection" type="o" direction="out"/>
|
||||
</signal>
|
||||
<signal name="CollectionChanged">
|
||||
<arg name="collection" type="o" direction="out"/>
|
||||
</signal>
|
||||
<method name="OpenSession">
|
||||
<arg type="v" direction="out"/>
|
||||
<arg name="algorithm" type="s" direction="in"/>
|
||||
<arg name="input" type="v" direction="in"/>
|
||||
<arg name="result" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="CreateCollection">
|
||||
<arg type="o" direction="out"/>
|
||||
<arg name="properties" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||
<arg name="alias" type="s" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="SearchItems">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="attributes" type="a{ss}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="StringStringMap"/>
|
||||
<arg name="locked" type="ao" direction="out"/>
|
||||
</method>
|
||||
<method name="Unlock">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="paths" type="ao" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="Lock">
|
||||
<arg type="ao" direction="out"/>
|
||||
<arg name="paths" type="ao" direction="in"/>
|
||||
<arg name="prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
<method name="GetSecrets">
|
||||
<arg type="a{o(oayays)}" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ObjectPathSecretMap"/>
|
||||
<arg name="items" type="ao" direction="in"/>
|
||||
<arg name="session" type="o" direction="in"/>
|
||||
</method>
|
||||
<method name="ReadAlias">
|
||||
<arg type="o" direction="out"/>
|
||||
<arg name="name" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="SetAlias">
|
||||
<arg name="name" type="s" direction="in"/>
|
||||
<arg name="collection" type="o" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
@ -1,4 +0,0 @@
|
||||
<interface name="org.freedesktop.Secret.Session">
|
||||
<method name="Close">
|
||||
</method>
|
||||
</interface>
|
@ -24,7 +24,7 @@ endif()
|
||||
|
||||
if(WITH_XC_FDOSECRETS)
|
||||
add_unit_test(NAME testguifdosecrets
|
||||
SOURCES TestGuiFdoSecrets.cpp ../util/TemporaryFile.cpp
|
||||
SOURCES TestGuiFdoSecrets.cpp ../util/TemporaryFile.cpp ../util/FdoSecretsProxy.cpp
|
||||
LIBS ${TEST_LIBRARIES}
|
||||
# The following doesn't work because dbus-run-session expects execname to be in PATH
|
||||
# dbus-run-session -- execname
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,14 +19,14 @@
|
||||
#define KEEPASSXC_TESTGUIFDOSECRETS_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QDBusConnection>
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "fdosecrets/GcryptMPI.h"
|
||||
#include "fdosecrets/objects/DBusTypes.h"
|
||||
#include "fdosecrets/dbus/DBusTypes.h"
|
||||
|
||||
class MainWindow;
|
||||
class Database;
|
||||
@ -42,7 +42,13 @@ namespace FdoSecrets
|
||||
class Item;
|
||||
class Prompt;
|
||||
class DhIetf1024Sha256Aes128CbcPkcs7;
|
||||
class DBusClient;
|
||||
} // namespace FdoSecrets
|
||||
class ServiceProxy;
|
||||
class CollectionProxy;
|
||||
class ItemProxy;
|
||||
class SessionProxy;
|
||||
class PromptProxy;
|
||||
|
||||
class QAbstractItemView;
|
||||
|
||||
@ -59,12 +65,11 @@ private slots:
|
||||
void cleanup();
|
||||
void cleanupTestCase();
|
||||
|
||||
void testDBusSpec();
|
||||
|
||||
void testServiceEnable();
|
||||
void testServiceEnableNoExposedDatabase();
|
||||
void testServiceSearch();
|
||||
void testServiceUnlock();
|
||||
void testServiceUnlockItems();
|
||||
void testServiceLock();
|
||||
|
||||
void testSessionOpen();
|
||||
@ -72,11 +77,15 @@ private slots:
|
||||
|
||||
void testCollectionCreate();
|
||||
void testCollectionDelete();
|
||||
void testCollectionChange();
|
||||
|
||||
void testItemCreate();
|
||||
void testItemChange();
|
||||
void testItemReplace();
|
||||
void testItemReplaceExistingLocked();
|
||||
void testItemSecret();
|
||||
void testItemDelete();
|
||||
void testItemLockState();
|
||||
|
||||
void testAlias();
|
||||
void testDefaultAliasAlwaysPresent();
|
||||
@ -88,21 +97,38 @@ private slots:
|
||||
void testDuplicateName();
|
||||
|
||||
protected slots:
|
||||
void createDatabaseCallback();
|
||||
void driveNewDatabaseWizard();
|
||||
bool driveAccessControlDialog(bool remember = true);
|
||||
|
||||
private:
|
||||
void lockDatabaseInBackend();
|
||||
void unlockDatabaseInBackend();
|
||||
QPointer<FdoSecrets::Service> enableService();
|
||||
QPointer<FdoSecrets::Session> openSession(FdoSecrets::Service* service, const QString& algo);
|
||||
QPointer<FdoSecrets::Collection> getDefaultCollection(FdoSecrets::Service* service);
|
||||
QPointer<FdoSecrets::Item> getFirstItem(FdoSecrets::Collection* coll);
|
||||
QPointer<FdoSecrets::Item> createItem(FdoSecrets::Session* sess,
|
||||
FdoSecrets::Collection* coll,
|
||||
const QString& label,
|
||||
const QString& pass,
|
||||
const StringStringMap& attr,
|
||||
bool replace);
|
||||
QSharedPointer<ServiceProxy> enableService();
|
||||
QSharedPointer<SessionProxy> openSession(const QSharedPointer<ServiceProxy>& service, const QString& algo);
|
||||
QSharedPointer<CollectionProxy> getDefaultCollection(const QSharedPointer<ServiceProxy>& service);
|
||||
QSharedPointer<ItemProxy> getFirstItem(const QSharedPointer<CollectionProxy>& coll);
|
||||
QSharedPointer<ItemProxy> createItem(const QSharedPointer<SessionProxy>& sess,
|
||||
const QSharedPointer<CollectionProxy>& coll,
|
||||
const QString& label,
|
||||
const QString& pass,
|
||||
const FdoSecrets::wire::StringStringMap& attr,
|
||||
bool replace,
|
||||
bool expectPrompt = false);
|
||||
template <typename Proxy> QSharedPointer<Proxy> getProxy(const QDBusObjectPath& path) const
|
||||
{
|
||||
auto ret = QSharedPointer<Proxy>{
|
||||
new Proxy(QStringLiteral("org.freedesktop.secrets"), path.path(), QDBusConnection::sessionBus())};
|
||||
if (!ret->isValid()) {
|
||||
return {};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T> T getSignalVariantArgument(const QVariant& arg)
|
||||
{
|
||||
const auto& in = arg.value<QDBusVariant>().variant();
|
||||
return qdbus_cast<T>(in);
|
||||
}
|
||||
|
||||
private:
|
||||
QScopedPointer<MainWindow> m_mainWindow;
|
||||
@ -111,6 +137,7 @@ private:
|
||||
QSharedPointer<Database> m_db;
|
||||
|
||||
QPointer<FdoSecretsPlugin> m_plugin;
|
||||
QSharedPointer<FdoSecrets::DBusClient> m_client;
|
||||
|
||||
// For DH session tests
|
||||
GcryptMPI m_serverPrivate;
|
||||
|
34
tests/util/FdoSecretsProxy.cpp
Normal file
34
tests/util/FdoSecretsProxy.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 "FdoSecretsProxy.h"
|
||||
|
||||
#define IMPL_PROXY(name) \
|
||||
name##Proxy::name##Proxy( \
|
||||
const QString& service, const QString& path, const QDBusConnection& connection, QObject* parent) \
|
||||
: QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) \
|
||||
{ \
|
||||
} \
|
||||
name##Proxy::~name##Proxy() = default;
|
||||
|
||||
IMPL_PROXY(Service)
|
||||
IMPL_PROXY(Collection)
|
||||
IMPL_PROXY(Item)
|
||||
IMPL_PROXY(Session)
|
||||
IMPL_PROXY(Prompt)
|
||||
|
||||
#undef IMPL_PROXY
|
402
tests/util/FdoSecretsProxy.h
Normal file
402
tests/util/FdoSecretsProxy.h
Normal file
@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* 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 KEEPASSXC_FDOSECRETSPROXY_H
|
||||
#define KEEPASSXC_FDOSECRETSPROXY_H
|
||||
|
||||
#include "fdosecrets/dbus/DBusTypes.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QtDBus>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* Mimic the interface of QDBusPendingReply so the same code can be used in test
|
||||
*/
|
||||
template <typename T> class PropertyReply
|
||||
{
|
||||
QDBusPendingReply<QDBusVariant> m_reply;
|
||||
|
||||
public:
|
||||
/*implicit*/ PropertyReply(const QDBusMessage& reply)
|
||||
: m_reply(reply)
|
||||
{
|
||||
}
|
||||
bool isFinished() const
|
||||
{
|
||||
return m_reply.isFinished();
|
||||
}
|
||||
bool isValid() const
|
||||
{
|
||||
return m_reply.isValid();
|
||||
}
|
||||
bool isError() const
|
||||
{
|
||||
return m_reply.isError();
|
||||
}
|
||||
QDBusError error() const
|
||||
{
|
||||
return m_reply.error();
|
||||
}
|
||||
T value() const
|
||||
{
|
||||
return qdbus_cast<T>(m_reply.value().variant());
|
||||
}
|
||||
template <int> T argumentAt() const
|
||||
{
|
||||
return value();
|
||||
}
|
||||
};
|
||||
|
||||
#define IMPL_GET_PROPERTY(name) \
|
||||
QDBusMessage msg = QDBusMessage::createMethodCall( \
|
||||
service(), path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Get")); \
|
||||
msg << interface() << QStringLiteral(#name); \
|
||||
return \
|
||||
{ \
|
||||
connection().call(msg, QDBus::BlockWithGui) \
|
||||
}
|
||||
|
||||
#define IMPL_SET_PROPERTY(name, value) \
|
||||
QDBusMessage msg = QDBusMessage::createMethodCall( \
|
||||
service(), path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Set")); \
|
||||
msg << interface() << QStringLiteral(#name) << QVariant::fromValue(QDBusVariant(QVariant::fromValue(value))); \
|
||||
return \
|
||||
{ \
|
||||
connection().call(msg, QDBus::BlockWithGui) \
|
||||
}
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Secret.Service
|
||||
*/
|
||||
class ServiceProxy : public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char* staticInterfaceName()
|
||||
{
|
||||
return "org.freedesktop.Secret.Service";
|
||||
}
|
||||
|
||||
public:
|
||||
ServiceProxy(const QString& service,
|
||||
const QString& path,
|
||||
const QDBusConnection& connection,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
~ServiceProxy() override;
|
||||
|
||||
inline PropertyReply<QList<QDBusObjectPath>> collections() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Collections);
|
||||
}
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<QDBusObjectPath, QDBusObjectPath> CreateCollection(const QVariantMap& properties,
|
||||
const QString& alias)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(properties) << QVariant::fromValue(alias);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("CreateCollection"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<FdoSecrets::wire::ObjectPathSecretMap> GetSecrets(const QList<QDBusObjectPath>& items,
|
||||
const QDBusObjectPath& session)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(items) << QVariant::fromValue(session);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("GetSecrets"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QList<QDBusObjectPath>, QDBusObjectPath> Lock(const QList<QDBusObjectPath>& paths)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(paths);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Lock"), argumentList)};
|
||||
}
|
||||
inline QDBusPendingReply<QDBusVariant, QDBusObjectPath> OpenSession(const QString& algorithm,
|
||||
const QDBusVariant& input)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(algorithm) << QVariant::fromValue(input);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("OpenSession"), argumentList)};
|
||||
}
|
||||
inline QDBusPendingReply<QDBusObjectPath> ReadAlias(const QString& name)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(name);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("ReadAlias"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QList<QDBusObjectPath>, QList<QDBusObjectPath>>
|
||||
SearchItems(FdoSecrets::wire::StringStringMap attributes)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(attributes);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("SearchItems"), argumentList)};
|
||||
}
|
||||
inline QDBusPendingReply<> SetAlias(const QString& name, const QDBusObjectPath& collection)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(name) << QVariant::fromValue(collection);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("SetAlias"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QList<QDBusObjectPath>, QDBusObjectPath> Unlock(const QList<QDBusObjectPath>& paths)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(paths);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Unlock"), argumentList)};
|
||||
}
|
||||
Q_SIGNALS: // SIGNALS
|
||||
void CollectionChanged(const QDBusObjectPath& collection);
|
||||
void CollectionCreated(const QDBusObjectPath& collection);
|
||||
void CollectionDeleted(const QDBusObjectPath& collection);
|
||||
};
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Secret.Collection
|
||||
*/
|
||||
class CollectionProxy : public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char* staticInterfaceName()
|
||||
{
|
||||
return "org.freedesktop.Secret.Collection";
|
||||
}
|
||||
|
||||
public:
|
||||
CollectionProxy(const QString& service,
|
||||
const QString& path,
|
||||
const QDBusConnection& connection,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
~CollectionProxy() override;
|
||||
|
||||
inline PropertyReply<qulonglong> created() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Created);
|
||||
}
|
||||
|
||||
inline PropertyReply<QList<QDBusObjectPath>> items() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Items);
|
||||
}
|
||||
|
||||
inline PropertyReply<QString> label() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Label);
|
||||
}
|
||||
inline QDBusPendingReply<> setLabel(const QString& value)
|
||||
{
|
||||
IMPL_SET_PROPERTY(Label, value);
|
||||
}
|
||||
|
||||
inline PropertyReply<bool> locked() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Locked);
|
||||
}
|
||||
|
||||
inline PropertyReply<qulonglong> modified() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Modified);
|
||||
}
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<QDBusObjectPath, QDBusObjectPath>
|
||||
CreateItem(const QVariantMap& properties, FdoSecrets::wire::Secret secret, bool replace)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(properties) << QVariant::fromValue(secret) << QVariant::fromValue(replace);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("CreateItem"), argumentList)};
|
||||
}
|
||||
inline QDBusPendingReply<QDBusObjectPath> Delete()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Delete"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QList<QDBusObjectPath>> SearchItems(FdoSecrets::wire::StringStringMap attributes)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(attributes);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("SearchItems"), argumentList)};
|
||||
}
|
||||
|
||||
Q_SIGNALS: // SIGNALS
|
||||
void ItemChanged(const QDBusObjectPath& item);
|
||||
void ItemCreated(const QDBusObjectPath& item);
|
||||
void ItemDeleted(const QDBusObjectPath& item);
|
||||
};
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Secret.Item
|
||||
*/
|
||||
class ItemProxy : public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char* staticInterfaceName()
|
||||
{
|
||||
return "org.freedesktop.Secret.Item";
|
||||
}
|
||||
|
||||
public:
|
||||
ItemProxy(const QString& service,
|
||||
const QString& path,
|
||||
const QDBusConnection& connection,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
~ItemProxy() override;
|
||||
|
||||
inline PropertyReply<FdoSecrets::wire::StringStringMap> attributes() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Attributes);
|
||||
}
|
||||
inline QDBusPendingReply<> setAttributes(FdoSecrets::wire::StringStringMap value)
|
||||
{
|
||||
IMPL_SET_PROPERTY(Attributes, value);
|
||||
}
|
||||
|
||||
inline PropertyReply<qulonglong> created() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Created);
|
||||
}
|
||||
|
||||
inline PropertyReply<QString> label() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Label);
|
||||
}
|
||||
inline QDBusPendingReply<> setLabel(const QString& value)
|
||||
{
|
||||
IMPL_SET_PROPERTY(Label, value);
|
||||
}
|
||||
|
||||
inline PropertyReply<bool> locked() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Locked);
|
||||
}
|
||||
|
||||
inline PropertyReply<qulonglong> modified() const
|
||||
{
|
||||
IMPL_GET_PROPERTY(Modified);
|
||||
}
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<QDBusObjectPath> Delete()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Delete"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<FdoSecrets::wire::Secret> GetSecret(const QDBusObjectPath& session)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(session);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("GetSecret"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<> SetSecret(FdoSecrets::wire::Secret secret)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(secret);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("SetSecret"), argumentList)};
|
||||
}
|
||||
|
||||
Q_SIGNALS: // SIGNALS
|
||||
};
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Secret.Session
|
||||
*/
|
||||
class SessionProxy : public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char* staticInterfaceName()
|
||||
{
|
||||
return "org.freedesktop.Secret.Session";
|
||||
}
|
||||
|
||||
public:
|
||||
SessionProxy(const QString& service,
|
||||
const QString& path,
|
||||
const QDBusConnection& connection,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
~SessionProxy() override;
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<> Close()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Close"), argumentList)};
|
||||
}
|
||||
|
||||
Q_SIGNALS: // SIGNALS
|
||||
};
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Secret.Prompt
|
||||
*/
|
||||
class PromptProxy : public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char* staticInterfaceName()
|
||||
{
|
||||
return "org.freedesktop.Secret.Prompt";
|
||||
}
|
||||
|
||||
public:
|
||||
PromptProxy(const QString& service,
|
||||
const QString& path,
|
||||
const QDBusConnection& connection,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
~PromptProxy() override;
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<> Dismiss()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Dismiss"), argumentList)};
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<> Prompt(const QString& windowId)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(windowId);
|
||||
return {callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("Prompt"), argumentList)};
|
||||
}
|
||||
|
||||
Q_SIGNALS: // SIGNALS
|
||||
void Completed(bool dismissed, const QDBusVariant& result);
|
||||
};
|
||||
|
||||
#undef IMPL_GET_PROPERTY
|
||||
#undef IMPL_SET_PROPERTY
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETSPROXY_H
|
Loading…
Reference in New Issue
Block a user