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:
Aetf 2021-02-05 15:07:59 -05:00 committed by GitHub
parent 33e6da33ca
commit 9a8a5a0006
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 5086 additions and 3075 deletions

View File

@ -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}},

View File

@ -151,7 +151,8 @@ public:
FdoSecrets_Enabled,
FdoSecrets_ShowNotification,
FdoSecrets_NoConfirmDeleteItem,
FdoSecrets_ConfirmDeleteItem,
FdoSecrets_ConfirmAccessItem,
KeeShare_QuietSuccess,
KeeShare_Own,

View File

@ -53,6 +53,15 @@ enum IconSize
Large
};
enum class AuthDecision
{
Undecided,
Allowed,
AllowedOnce,
Denied,
DeniedOnce,
};
template <typename T> struct AddConst
{
typedef const T Type;

View File

@ -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()

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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

View File

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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)

View 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

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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;
}

View 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

View 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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;%1 &lt;/span&gt;is requesting access to the following entries:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View 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);
}
});
}

View 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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;
};

View File

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If recycle bin is enabled for the database, entries will be moved to recycle bin directly. Otherwise, they will be deleted without confirmation.&lt;/p&gt;&lt;p&gt;You will still be prompted if any entries are referenced by others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled, any attempt to read a password must be confirmed. Otherwise, clients can read passwords without confirmation when the database is unlocked.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; 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;&quot;&gt;This setting does not override disabling recycle bin prompts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -32,6 +32,7 @@ private slots:
void testDhIetf1024Sha256Aes128CbcPkcs7();
void testCrazyAttributeKey();
void testSpecialCharsInAttributeValue();
void testDBusPathParse();
};
#endif // KEEPASSXC_TESTFDOSECRETS_H

Binary file not shown.

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,4 +0,0 @@
<interface name="org.freedesktop.Secret.Session">
<method name="Close">
</method>
</interface>

View File

@ -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

View File

@ -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;

View 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

View 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