mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Add Freedesktop.org Secret Storage Spec Server Side API (Fix #1403)
This plugin implements the Secret Storage specification version 0.2. While running KeePassXC, it acts as a Secret Service server, registered on DBus, so clients like seahorse, python-secretstorage, or other implementations can connect and access the exposed database in KeePassXC. Squashed commits: - Initial code - Add SessionAdaptor and fix build - The skeletons for all dbus objects are in place - Implement collection creation and deletion - Emit collectionChanged signal - Implement app-wise settings page - Implement error message on GUI - Implement settings - Fix uuid to dbus path - Implement app level settings - Add freedesktop logo - Implement database settings page - Change database settings to a treeview - Move all settings read/write to one place - Rename SecretServiceOptionsPage to SettingsWidgetFdoSecrets - Fix selected group can not be saved if the user hasn't click on the item - Show selected group per database in app settings - Disable editing of various readonly widgets - Remove unused warning about non exposed database - Fix method signature on dbus adaptors - Fix type derived from DBusObject not recognized as QDBusContext - Resolve a few TODOs around error handling - Remove const when passing DBus exposed objects - Move dismiss to PromptBase - Implement per collection locking/unlocking - Fix const correctness on Item::setSecret - Implement SecretService::getSecrets - Rework the signal connections around collections. - Remove generateId from DBusObject - Per spec, use encoded label as DBus object path for collections - Fix some corner cases around collection name changes - Implement alias - Fix wrong alias dbus path - Implement encryption per spec - Cleanup SessionCipher - Implement searchItems for SecretService - Use Tools::uuidToHex - Implement Item attributes and delete - Implement createItem - Always check if the database is unlocked before perform any operation - Add missing ReadAlias/SetAlias on service - Reorganize and fix OpenSession always returning empty output - Overhaul error handling - Make sure default alias is always present - Remove collection aliases early in doDelete - Handles all content types, fix setProperties not working - Fix sometimes there is an extraneous leading zero when converting from MPI - Fix session encryption negotiation - Do not expose recycle bin - Protect against the methods not called from DBus - Also emit collectionChanged signal when lock state changes - Show notification when entry secret is requested - Add a README file - Actually close session when client disconnects - Gracefully return alternative label when collection is locked - Reorganize, rename secretservice to fdosecrets - Fix issues reported by clazy - Unify UI strings and fix icon - Implement a setting to skip confirmation when deleting entries from DBus - Remove some unused debugging log - Simply ignore errors when DBus context is not available. QtDBus won't set QDBusContext when deliver property get/set, and there is no way to get a QDBusMessage in property getter/setter. - Simplify GcryptMPI using std::unique_ptr and add unit test - Format code in fdosecrets - Move DBusReturnImpl to details namespace - Fix crash when locking a database: don't modify exposedGroup setting in customData when database is deleted - Make sure Collection::searchItems works, whether it's locked or not - Fix FdoSecrets::Collection becomes empty after a database reload - Fix crash when looping while modifying the list
This commit is contained in:
parent
d93f33f514
commit
e121f4bc28
@ -50,6 +50,9 @@ option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
|
||||
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
|
||||
if(UNIX AND NOT APPLE)
|
||||
option(WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API." OFF)
|
||||
endif()
|
||||
if(APPLE)
|
||||
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
||||
endif()
|
||||
@ -65,6 +68,9 @@ if(WITH_XC_ALL)
|
||||
if(APPLE)
|
||||
set(WITH_XC_TOUCHID ON)
|
||||
endif()
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(WITH_XC_FDOSECRETS ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
|
||||
|
4
COPYING
4
COPYING
@ -241,3 +241,7 @@ License: LGPL-2.1
|
||||
Files: share/macosx/dmg-background.tiff
|
||||
Copyright: 2008-2014, Andrey Tarantsov
|
||||
License: MIT
|
||||
|
||||
Files: share/icons/application/scalable/apps/freedesktop.svg
|
||||
Copyright: GPL-2+
|
||||
Comment: from Freedesktop.org website
|
||||
|
@ -102,6 +102,7 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
|
||||
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (e.g., favicon downloading) (default: OFF)
|
||||
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
|
||||
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
|
||||
-DWITH_XC_FDOSECRETS=[ON|OFF] (Linux Only) Enable/Disable Freedesktop.org Secrets Service support (default:OFF)
|
||||
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
|
||||
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare signed containers, requires libquazip5 (default: OFF)
|
||||
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
|
||||
|
92
share/icons/application/scalable/apps/freedesktop.svg
Normal file
92
share/icons/application/scalable/apps/freedesktop.svg
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="108.7505"
|
||||
height="91.166321"
|
||||
viewBox="0 0 87.000389 72.933061"
|
||||
id="svg2"
|
||||
sodipodi:docname="freedesktop.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<defs
|
||||
id="defs14" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2106"
|
||||
id="namedview12"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.7980996"
|
||||
inkscape:cx="-97.45169"
|
||||
inkscape:cy="25.551539"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<metadata
|
||||
id="metadata57">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0.01402783,0.01402783)"
|
||||
id="g37"
|
||||
style="fill:#ffffff;fill-rule:nonzero;stroke:#3b80ae;stroke-width:2.45880008;stroke-miterlimit:4">
|
||||
<g
|
||||
id="g39">
|
||||
<path
|
||||
d="M 85.277,40.796 C 87.335,48.68 82.61,56.738 74.726,58.795 L 27.143,71.21 C 19.259,73.267 11.2,68.543 9.143,60.658 L 1.695,32.108 C -0.362,24.224 4.362,16.166 12.246,14.109 L 59.83,1.695 c 7.884,-2.057 15.942,2.667 17.999,10.551 l 7.449,28.55 z"
|
||||
id="path41"
|
||||
style="stroke:#bababa"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 80.444,39.778 c 1.749,7.854 -1.816,13.621 -9.504,15.447 L 28.704,66.245 C 21.135,68.641 14.615,65.064 12.866,57.409 L 6.53,33.127 C 4.781,24.982 7.239,20.238 16.033,17.68 L 58.27,6.661 c 8.144,-1.826 14.089,1.363 15.838,8.835 z"
|
||||
id="path43"
|
||||
style="fill:#3b80ae;stroke:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<path
|
||||
d="M 45.542,51.793 24.104,31.102 62.204,26.709 Z"
|
||||
id="path45"
|
||||
style="opacity:0.5;fill:none;stroke:#ffffff"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 72.325,28.769 c 0.405,1.55 -0.525,3.136 -2.075,3.541 l -12.331,3.217 c -1.551,0.404 -3.137,-0.525 -3.542,-2.076 L 52.082,24.65 c -0.405,-1.551 0.524,-3.137 2.076,-3.542 l 12.33,-3.217 c 1.551,-0.405 3.137,0.525 3.542,2.076 l 2.295,8.801 z"
|
||||
id="path47"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 36.51,33.625 c 0.496,1.9 -0.645,3.844 -2.545,4.34 l -15.112,3.943 c -1.901,0.496 -3.845,-0.644 -4.34,-2.544 L 11.699,28.578 c -0.496,-1.901 0.644,-3.844 2.544,-4.34 l 15.113,-3.942 c 1.901,-0.496 3.845,0.643 4.34,2.544 l 2.814,10.786 z"
|
||||
id="path49"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 52.493,53.208 c 0.278,1.065 -0.36,2.154 -1.425,2.432 L 42.6,57.848 c -1.064,0.277 -2.153,-0.36 -2.431,-1.426 l -1.577,-6.043 c -0.277,-1.064 0.36,-2.153 1.425,-2.432 l 8.468,-2.209 c 1.064,-0.277 2.154,0.361 2.431,1.426 l 1.577,6.043 z"
|
||||
id="path51"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
@ -194,6 +194,9 @@ add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible wit
|
||||
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
||||
if(UNIX AND NOT APPLE)
|
||||
add_feature_info(FdoSecrets WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API.")
|
||||
endif()
|
||||
if(APPLE)
|
||||
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
|
||||
endif()
|
||||
@ -226,6 +229,11 @@ if(WITH_XC_SSHAGENT)
|
||||
set(sshagent_LIB sshagent)
|
||||
endif()
|
||||
|
||||
add_subdirectory(fdosecrets)
|
||||
if(WITH_XC_FDOSECRETS)
|
||||
set(fdosecrets_LIB fdosecrets)
|
||||
endif()
|
||||
|
||||
set(autotype_SOURCES
|
||||
core/Tools.cpp
|
||||
autotype/AutoType.cpp
|
||||
@ -270,6 +278,7 @@ target_link_libraries(keepassx_core
|
||||
autotype
|
||||
${keepassxcbrowser_LIB}
|
||||
${qrcode_LIB}
|
||||
${fdosecrets_LIB}
|
||||
Qt5::Core
|
||||
Qt5::Concurrent
|
||||
Qt5::Network
|
||||
|
@ -22,6 +22,7 @@
|
||||
#cmakedefine WITH_XC_KEESHARE_SECURE
|
||||
#cmakedefine WITH_XC_UPDATECHECK
|
||||
#cmakedefine WITH_XC_TOUCHID
|
||||
#cmakedefine WITH_XC_FDOSECRETS
|
||||
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE_RELEASE
|
||||
|
36
src/fdosecrets/CMakeLists.txt
Normal file
36
src/fdosecrets/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
if(WITH_XC_FDOSECRETS)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
add_library(fdosecrets STATIC
|
||||
# app settings page
|
||||
FdoSecretsPlugin.cpp
|
||||
widgets/SettingsWidgetFdoSecrets.cpp
|
||||
|
||||
# per database settings page
|
||||
DatabaseSettingsPageFdoSecrets.cpp
|
||||
widgets/DatabaseSettingsWidgetFdoSecrets.cpp
|
||||
|
||||
# setting storage
|
||||
FdoSecretsSettings.cpp
|
||||
|
||||
# gcrypt MPI wrapper
|
||||
GcryptMPI.cpp
|
||||
|
||||
# dbus objects
|
||||
objects/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
|
||||
)
|
||||
target_link_libraries(fdosecrets Qt5::Core Qt5::Widgets Qt5::DBus ${GCRYPT_LIBRARIES})
|
||||
endif()
|
49
src/fdosecrets/DatabaseSettingsPageFdoSecrets.cpp
Normal file
49
src/fdosecrets/DatabaseSettingsPageFdoSecrets.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 "DatabaseSettingsPageFdoSecrets.h"
|
||||
|
||||
#include "fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.h"
|
||||
|
||||
#include "core/FilePath.h"
|
||||
|
||||
QString DatabaseSettingsPageFdoSecrets::name()
|
||||
{
|
||||
return QObject::tr("Secret Service Integration");
|
||||
}
|
||||
|
||||
QIcon DatabaseSettingsPageFdoSecrets::icon()
|
||||
{
|
||||
return filePath()->icon(QStringLiteral("apps"), QStringLiteral("freedesktop"));
|
||||
}
|
||||
|
||||
QWidget* DatabaseSettingsPageFdoSecrets::createWidget()
|
||||
{
|
||||
return new DatabaseSettingsWidgetFdoSecrets;
|
||||
}
|
||||
|
||||
void DatabaseSettingsPageFdoSecrets::loadSettings(QWidget* widget, QSharedPointer<Database> db)
|
||||
{
|
||||
auto settingsWidget = qobject_cast<DatabaseSettingsWidgetFdoSecrets*>(widget);
|
||||
settingsWidget->loadSettings(db);
|
||||
}
|
||||
|
||||
void DatabaseSettingsPageFdoSecrets::saveSettings(QWidget* widget)
|
||||
{
|
||||
auto settingsWidget = qobject_cast<DatabaseSettingsWidgetFdoSecrets*>(widget);
|
||||
settingsWidget->saveSettings();
|
||||
}
|
36
src/fdosecrets/DatabaseSettingsPageFdoSecrets.h
Normal file
36
src/fdosecrets/DatabaseSettingsPageFdoSecrets.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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_DATABASESETTINGSPAGEFDOSECRETS_H
|
||||
#define KEEPASSXC_DATABASESETTINGSPAGEFDOSECRETS_H
|
||||
|
||||
#include "gui/dbsettings/DatabaseSettingsDialog.h"
|
||||
|
||||
class DatabaseSettingsPageFdoSecrets : public IDatabaseSettingsPage
|
||||
{
|
||||
Q_DISABLE_COPY(DatabaseSettingsPageFdoSecrets)
|
||||
public:
|
||||
DatabaseSettingsPageFdoSecrets() = default;
|
||||
|
||||
QString name() override;
|
||||
QIcon icon() override;
|
||||
QWidget* createWidget() override;
|
||||
void loadSettings(QWidget* widget, QSharedPointer<Database> db) override;
|
||||
void saveSettings(QWidget* widget) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSPAGEFDOSECRETS_H
|
88
src/fdosecrets/FdoSecretsPlugin.cpp
Normal file
88
src/fdosecrets/FdoSecretsPlugin.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 "FdoSecretsPlugin.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/objects/DBusTypes.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/widgets/SettingsWidgetFdoSecrets.h"
|
||||
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using FdoSecrets::Service;
|
||||
|
||||
FdoSecretsPlugin::FdoSecretsPlugin(DatabaseTabWidget* tabWidget)
|
||||
: m_dbTabs(tabWidget)
|
||||
{
|
||||
FdoSecrets::registerDBusTypes();
|
||||
}
|
||||
|
||||
QWidget* FdoSecretsPlugin::createWidget()
|
||||
{
|
||||
return new SettingsWidgetFdoSecrets(this);
|
||||
}
|
||||
|
||||
void FdoSecretsPlugin::loadSettings(QWidget* widget)
|
||||
{
|
||||
qobject_cast<SettingsWidgetFdoSecrets*>(widget)->loadSettings();
|
||||
}
|
||||
|
||||
void FdoSecretsPlugin::saveSettings(QWidget* widget)
|
||||
{
|
||||
qobject_cast<SettingsWidgetFdoSecrets*>(widget)->saveSettings();
|
||||
updateServiceState();
|
||||
}
|
||||
|
||||
void FdoSecretsPlugin::updateServiceState()
|
||||
{
|
||||
if (FdoSecrets::settings()->isEnabled()) {
|
||||
if (!m_secretService && m_dbTabs) {
|
||||
m_secretService.reset(new Service(this, m_dbTabs));
|
||||
connect(m_secretService.data(), &Service::error, this, [this](const QString& msg) {
|
||||
emit error(tr("Fdo Secret Service: %1").arg(msg));
|
||||
});
|
||||
if (!m_secretService->initialize()) {
|
||||
m_secretService.reset();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_secretService) {
|
||||
m_secretService.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Service* FdoSecretsPlugin::serviceInstance() const
|
||||
{
|
||||
return m_secretService.data();
|
||||
}
|
||||
|
||||
void FdoSecretsPlugin::emitRequestSwitchToDatabases()
|
||||
{
|
||||
emit requestSwitchToDatabases();
|
||||
}
|
||||
|
||||
void FdoSecretsPlugin::emitRequestShowNotification(const QString& msg, const QString& title)
|
||||
{
|
||||
if (!FdoSecrets::settings()->showNotification()) {
|
||||
return;
|
||||
}
|
||||
emit requestShowNotification(msg, title, 10000);
|
||||
}
|
76
src/fdosecrets/FdoSecretsPlugin.h
Normal file
76
src/fdosecrets/FdoSecretsPlugin.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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_FDOSECRETSPLUGIN_H
|
||||
#define KEEPASSXC_FDOSECRETSPLUGIN_H
|
||||
|
||||
#include "core/FilePath.h"
|
||||
#include "gui/ApplicationSettingsWidget.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class DatabaseTabWidget;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Service;
|
||||
} // namespace FdoSecrets
|
||||
|
||||
class FdoSecretsPlugin : public QObject, public ISettingsPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FdoSecretsPlugin(DatabaseTabWidget* tabWidget);
|
||||
~FdoSecretsPlugin() override = default;
|
||||
|
||||
QString name() override
|
||||
{
|
||||
return QObject::tr("Secret Service Integration");
|
||||
}
|
||||
|
||||
QIcon icon() override
|
||||
{
|
||||
return FilePath::instance()->icon("apps", "freedesktop");
|
||||
}
|
||||
|
||||
QWidget* createWidget() override;
|
||||
void loadSettings(QWidget* widget) override;
|
||||
void saveSettings(QWidget* widget) override;
|
||||
|
||||
void updateServiceState();
|
||||
|
||||
/**
|
||||
* @return The service instance, can be nullptr if the service is disabled.
|
||||
*/
|
||||
FdoSecrets::Service* serviceInstance() const;
|
||||
|
||||
public slots:
|
||||
void emitRequestSwitchToDatabases();
|
||||
void emitRequestShowNotification(const QString& msg, const QString& title = {});
|
||||
|
||||
signals:
|
||||
void error(const QString& msg);
|
||||
void requestSwitchToDatabases();
|
||||
void requestShowNotification(const QString& msg, const QString& title, int msTimeoutHint);
|
||||
|
||||
private:
|
||||
QPointer<DatabaseTabWidget> m_dbTabs;
|
||||
QScopedPointer<FdoSecrets::Service> m_secretService;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETSPLUGIN_H
|
103
src/fdosecrets/FdoSecretsSettings.cpp
Normal file
103
src/fdosecrets/FdoSecretsSettings.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 "FdoSecretsSettings.h"
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/CustomData.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Metadata.h"
|
||||
|
||||
namespace Keys
|
||||
{
|
||||
|
||||
constexpr auto FdoSecretsEnabled = "FdoSecrets/Enabled";
|
||||
constexpr auto FdoSecretsShowNotification = "FdoSecrets/ShowNotification";
|
||||
constexpr auto FdoSecretsNoConfirmDeleteItem = "FdoSecrets/NoConfirmDeleteItem";
|
||||
|
||||
namespace Db
|
||||
{
|
||||
constexpr auto FdoSecretsExposedGroup = "FDO_SECRETS_EXPOSED_GROUP";
|
||||
} // namespace Db
|
||||
|
||||
} // namespace Keys
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
FdoSecretsSettings* FdoSecretsSettings::m_instance = nullptr;
|
||||
|
||||
FdoSecretsSettings* FdoSecretsSettings::instance()
|
||||
{
|
||||
if (!m_instance) {
|
||||
m_instance = new FdoSecretsSettings;
|
||||
}
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
bool FdoSecretsSettings::isEnabled() const
|
||||
{
|
||||
return config()->get(Keys::FdoSecretsEnabled, false).toBool();
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setEnabled(bool enabled)
|
||||
{
|
||||
config()->set(Keys::FdoSecretsEnabled, enabled);
|
||||
}
|
||||
|
||||
bool FdoSecretsSettings::showNotification() const
|
||||
{
|
||||
return config()->get(Keys::FdoSecretsShowNotification, true).toBool();
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setShowNotification(bool show)
|
||||
{
|
||||
config()->set(Keys::FdoSecretsShowNotification, show);
|
||||
}
|
||||
|
||||
bool FdoSecretsSettings::noConfirmDeleteItem() const
|
||||
{
|
||||
return config()->get(Keys::FdoSecretsNoConfirmDeleteItem, false).toBool();
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setNoConfirmDeleteItem(bool noConfirm)
|
||||
{
|
||||
config()->set(Keys::FdoSecretsNoConfirmDeleteItem, noConfirm);
|
||||
}
|
||||
|
||||
QUuid FdoSecretsSettings::exposedGroup(const QSharedPointer<Database>& db) const
|
||||
{
|
||||
return exposedGroup(db.data());
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setExposedGroup(const QSharedPointer<Database>& db,
|
||||
const QUuid& group) // clazy:exclude=function-args-by-value
|
||||
{
|
||||
setExposedGroup(db.data(), group);
|
||||
}
|
||||
|
||||
QUuid FdoSecretsSettings::exposedGroup(Database* db) const
|
||||
{
|
||||
return {db->metadata()->customData()->value(Keys::Db::FdoSecretsExposedGroup)};
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setExposedGroup(Database* db, const QUuid& group) // clazy:exclude=function-args-by-value
|
||||
{
|
||||
db->metadata()->customData()->set(Keys::Db::FdoSecretsExposedGroup, group.toString());
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
62
src/fdosecrets/FdoSecretsSettings.h
Normal file
62
src/fdosecrets/FdoSecretsSettings.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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_FDOSECRETSSETTINGS_H
|
||||
#define KEEPASSXC_FDOSECRETSSETTINGS_H
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
|
||||
class Database;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class FdoSecretsSettings
|
||||
{
|
||||
public:
|
||||
FdoSecretsSettings() = default;
|
||||
static FdoSecretsSettings* instance();
|
||||
|
||||
bool isEnabled() const;
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
bool showNotification() const;
|
||||
void setShowNotification(bool show);
|
||||
|
||||
bool noConfirmDeleteItem() const;
|
||||
void setNoConfirmDeleteItem(bool noConfirm);
|
||||
|
||||
// Per db settings
|
||||
|
||||
QUuid exposedGroup(const QSharedPointer<Database>& db) const;
|
||||
void setExposedGroup(const QSharedPointer<Database>& db, const QUuid& group);
|
||||
QUuid exposedGroup(Database* db) const;
|
||||
void setExposedGroup(Database* db, const QUuid& group);
|
||||
|
||||
private:
|
||||
static FdoSecretsSettings* m_instance;
|
||||
};
|
||||
|
||||
inline FdoSecretsSettings* settings()
|
||||
{
|
||||
return FdoSecretsSettings::instance();
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETSSETTINGS_H
|
63
src/fdosecrets/GcryptMPI.cpp
Normal file
63
src/fdosecrets/GcryptMPI.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 "GcryptMPI.h"
|
||||
|
||||
GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure, gcry_mpi_format format)
|
||||
{
|
||||
auto bufLen = static_cast<size_t>(bytes.size());
|
||||
|
||||
const char* buf = nullptr;
|
||||
|
||||
// gcry_mpi_scan uses secure memory only if input buffer is secure memory, so we have to make a copy
|
||||
GcryptMemPtr secureBuf;
|
||||
if (secure) {
|
||||
secureBuf.reset(static_cast<char*>(gcry_malloc_secure(bufLen)));
|
||||
memcpy(secureBuf.get(), bytes.data(), bufLen);
|
||||
buf = secureBuf.get();
|
||||
} else {
|
||||
buf = bytes.data();
|
||||
}
|
||||
|
||||
gcry_mpi_t rawMpi;
|
||||
auto err = gcry_mpi_scan(&rawMpi, format, buf, format == GCRYMPI_FMT_HEX ? 0 : bufLen, nullptr);
|
||||
GcryptMPI mpi(rawMpi);
|
||||
|
||||
if (gcry_err_code(err) != GPG_ERR_NO_ERROR) {
|
||||
mpi.reset();
|
||||
}
|
||||
|
||||
return mpi;
|
||||
}
|
||||
|
||||
GcryptMPI MpiFromHex(const char* hex, bool secure)
|
||||
{
|
||||
return MpiFromBytes(QByteArray::fromRawData(hex, static_cast<int>(strlen(hex) + 1)), secure, GCRYMPI_FMT_HEX);
|
||||
}
|
||||
|
||||
QByteArray MpiToBytes(const GcryptMPI& mpi)
|
||||
{
|
||||
unsigned char* buf = nullptr;
|
||||
size_t buflen = 0;
|
||||
gcry_mpi_aprint(GCRYMPI_FMT_USG, &buf, &buflen, mpi.get());
|
||||
|
||||
QByteArray bytes(reinterpret_cast<char*>(buf), static_cast<int>(buflen));
|
||||
|
||||
gcry_free(buf);
|
||||
|
||||
return bytes;
|
||||
}
|
64
src/fdosecrets/GcryptMPI.h
Normal file
64
src/fdosecrets/GcryptMPI.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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_GCRYPTMPI_H
|
||||
#define KEEPASSXC_GCRYPTMPI_H
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
#include <gcrypt.h>
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
template <typename D, D fn> using deleter_from_fn = std::integral_constant<D, fn>;
|
||||
|
||||
/**
|
||||
* A simple RAII wrapper for gcry_mpi_t
|
||||
*/
|
||||
using GcryptMPI = std::unique_ptr<gcry_mpi, deleter_from_fn<decltype(&gcry_mpi_release), &gcry_mpi_release>>;
|
||||
|
||||
/**
|
||||
* A simple RAII wrapper for libgcrypt allocated memory
|
||||
*/
|
||||
using GcryptMemPtr = std::unique_ptr<char, deleter_from_fn<decltype(&gcry_free), &gcry_free>>;
|
||||
|
||||
/**
|
||||
* Parse a QByteArray as MPI
|
||||
* @param bytes
|
||||
* @param secure
|
||||
* @param format
|
||||
* @return
|
||||
*/
|
||||
GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure = true, gcry_mpi_format format = GCRYMPI_FMT_USG);
|
||||
|
||||
/**
|
||||
* Parse MPI from hex data
|
||||
* @param hex
|
||||
* @param secure
|
||||
* @return
|
||||
*/
|
||||
GcryptMPI MpiFromHex(const char* hex, bool secure = true);
|
||||
|
||||
/**
|
||||
* Dump MPI to bytes in USG format
|
||||
* @param mpi
|
||||
* @return
|
||||
*/
|
||||
QByteArray MpiToBytes(const GcryptMPI& mpi);
|
||||
|
||||
#endif // KEEPASSXC_GCRYPTMPI_H
|
42
src/fdosecrets/README.md
Normal file
42
src/fdosecrets/README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Freedesktop.org Secret Storage Spec Server Side API
|
||||
|
||||
This plugin implements the [Secret Storage specification][secrets] version 0.2. While running KeePassXC, it acts as a
|
||||
Secret Service server, registered on DBus, so clients like seahorse, python-secretstorage, or other implementations
|
||||
can connect and access the exposed database in KeePassXC.
|
||||
|
||||
[secrets]: (https://www.freedesktop.org/wiki/Specifications/secret-storage-spec/)
|
||||
|
||||
## Configurable settings
|
||||
|
||||
* The user can specify if a database is exposed on DBus, and which group is exposed.
|
||||
* Whether to show desktop notification is shown when an entry is retrived.
|
||||
* Whether to skip confirmation for entries deleted from DBus
|
||||
|
||||
## Implemented Attributes on Item Object
|
||||
|
||||
The following attributes are exposed:
|
||||
|
||||
|Key|Value|
|
||||
|:---:|:---:|
|
||||
|Title|The entry title|
|
||||
|UserName|The entry user name|
|
||||
|URL|The entry URL|
|
||||
|Notes|The entry notes|
|
||||
|
||||
In addition, all non-protected custom attributes are also exposed.
|
||||
|
||||
## Architecture
|
||||
|
||||
* `FdoSecrets::Service` is the top level DBus service
|
||||
* There is one and only one `FdoSecrets::Collection` per opened database tab
|
||||
* Each entry under the exposed database group has a corresponding `FdoSecrets::Item` DBus object.
|
||||
|
||||
### Signal connections
|
||||
|
||||
- Collections are created when a corresponding database tab opened
|
||||
- If the database is locked, a collection is created
|
||||
- When the database is unlocked, collection populates its children
|
||||
- If the unlocked database's exposed group is none, collection deletes itself
|
||||
- If the database's exposed group changes, collection repopulates
|
||||
- If the database's exposed group changes to none, collection deletes itself
|
||||
- If the database's exposed group changes from none, the service recreates a collection
|
654
src/fdosecrets/objects/Collection.cpp
Normal file
654
src/fdosecrets/objects/Collection.cpp
Normal file
@ -0,0 +1,654 @@
|
||||
/*
|
||||
* 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 "Collection.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/EntrySearcher.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
Collection::Collection(Service* parent, DatabaseWidget* backend)
|
||||
: DBusObject(parent)
|
||||
, m_backend(backend)
|
||||
, m_exposedGroup(nullptr)
|
||||
, m_registered(false)
|
||||
{
|
||||
// whenever the file path or the database object itself change, we do a full reload.
|
||||
connect(backend, &DatabaseWidget::databaseFilePathChanged, this, &Collection::reloadBackend);
|
||||
connect(backend, &DatabaseWidget::databaseReplaced, this, &Collection::reloadBackend);
|
||||
|
||||
// also remember to clear/populate the database when lock state changes.
|
||||
connect(backend, &DatabaseWidget::databaseUnlocked, this, &Collection::onDatabaseLockChanged);
|
||||
connect(backend, &DatabaseWidget::databaseLocked, this, &Collection::onDatabaseLockChanged);
|
||||
|
||||
reloadBackend();
|
||||
}
|
||||
|
||||
void Collection::reloadBackend()
|
||||
{
|
||||
if (m_registered) {
|
||||
// delete all items
|
||||
// this has to be done because the backend is actually still there, just we don't expose them
|
||||
// NOTE: Do NOT use a for loop, because Item::doDelete will remove itself from m_items.
|
||||
while (!m_items.isEmpty()) {
|
||||
m_items.first()->doDelete();
|
||||
}
|
||||
cleanupConnections();
|
||||
|
||||
unregisterCurrentPath();
|
||||
m_registered = false;
|
||||
}
|
||||
|
||||
// make sure we have updated copy of the filepath, which is used to identify the database.
|
||||
m_backendPath = m_backend->database()->filePath();
|
||||
|
||||
// the database may not have a name (derived from filePath) yet, which may happen if it's newly created.
|
||||
// defer the registration to next time a file path change happens.
|
||||
if (!name().isEmpty()) {
|
||||
registerWithPath(
|
||||
QStringLiteral(DBUS_PATH_TEMPLATE_COLLECTION).arg(p()->objectPath().path(), encodePath(name())),
|
||||
new CollectionAdaptor(this));
|
||||
m_registered = true;
|
||||
}
|
||||
|
||||
// populate contents after expose on dbus, because items rely on parent's dbus object path
|
||||
if (!backendLocked()) {
|
||||
populateContents();
|
||||
} else {
|
||||
cleanupConnections();
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::ensureBackend() const
|
||||
{
|
||||
if (!m_backend) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::ensureUnlocked() const
|
||||
{
|
||||
if (backendLocked()) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_IS_LOCKED));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Item*>> Collection::items() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
return m_items;
|
||||
}
|
||||
|
||||
DBusReturn<QString> Collection::label() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (backendLocked()) {
|
||||
return name();
|
||||
}
|
||||
return m_backend->database()->metadata()->name();
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::setLabel(const QString& label)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
m_backend->database()->metadata()->setName(label);
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<bool> Collection::locked() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
return backendLocked();
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Collection::created() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
return static_cast<qulonglong>(
|
||||
m_backend->database()->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Collection::modified() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
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>(
|
||||
m_backend->database()->rootGroup()->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
DBusReturn<PromptBase*> Collection::deleteCollection()
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Delete means close database
|
||||
auto prompt = new DeleteCollectionPrompt(service(), this);
|
||||
if (backendLocked()) {
|
||||
// this won't raise a dialog, immediate execute
|
||||
auto pret = prompt->prompt({});
|
||||
if (pret.isError()) {
|
||||
return pret;
|
||||
}
|
||||
prompt = nullptr;
|
||||
}
|
||||
// defer the close to the prompt
|
||||
return prompt;
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Item*>> Collection::searchItems(const StringStringMap& attributes)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
// 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*>{};
|
||||
}
|
||||
|
||||
// shortcut logic for Uuid/Path attributes, as they can uniquely identify an item.
|
||||
if (attributes.contains(ItemAttributes::UuidKey)) {
|
||||
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*>{};
|
||||
}
|
||||
}
|
||||
|
||||
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*>{};
|
||||
}
|
||||
}
|
||||
|
||||
static QMap<QString, QString> attrKeyToField{
|
||||
{EntryAttributes::TitleKey, QStringLiteral("title")},
|
||||
{EntryAttributes::UserNameKey, QStringLiteral("user")},
|
||||
{EntryAttributes::URLKey, QStringLiteral("url")},
|
||||
{EntryAttributes::NotesKey, QStringLiteral("notes")},
|
||||
};
|
||||
|
||||
QStringList terms;
|
||||
for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
|
||||
if (it.key() == EntryAttributes::PasswordKey) {
|
||||
continue;
|
||||
}
|
||||
auto field = attrKeyToField.value(it.key(), QStringLiteral("_") + Item::encodeAttributeKey(it.key()));
|
||||
terms << QStringLiteral(R"raw(+%1:"%2")raw").arg(field, it.value());
|
||||
}
|
||||
|
||||
QList<Item*> items;
|
||||
const auto foundEntries = EntrySearcher().search(terms.join(' '), m_exposedGroup);
|
||||
items.reserve(foundEntries.size());
|
||||
for (const auto& entry : foundEntries) {
|
||||
items << m_entryToItem.value(entry);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
DBusReturn<Item*>
|
||||
Collection::createItem(const QVariantMap& properties, const SecretStruct& secret, bool replace, PromptBase*& prompt)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
prompt = nullptr;
|
||||
|
||||
Item* item = nullptr;
|
||||
QString itemPath;
|
||||
StringStringMap attributes;
|
||||
|
||||
// check existing item using attributes
|
||||
auto iterAttr = properties.find(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes"));
|
||||
if (iterAttr != properties.end()) {
|
||||
attributes = qdbus_cast<StringStringMap>(iterAttr.value().value<QDBusArgument>());
|
||||
|
||||
itemPath = attributes.value(ItemAttributes::PathKey);
|
||||
|
||||
auto existings = searchItems(attributes);
|
||||
if (existings.isError()) {
|
||||
return existings;
|
||||
}
|
||||
if (!existings.value().isEmpty() && replace) {
|
||||
item = existings.value().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->applyGroupIconTo(entry);
|
||||
|
||||
entry->setGroup(group);
|
||||
|
||||
// when creation finishes in backend, we will already have item
|
||||
item = m_entryToItem.value(entry, nullptr);
|
||||
Q_ASSERT(item);
|
||||
}
|
||||
|
||||
ret = item->setProperties(properties);
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = item->setSecret(secret);
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::setProperties(const QVariantMap& properties)
|
||||
{
|
||||
auto label = properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_COLLECTION ".Label")).toString();
|
||||
|
||||
auto ret = setLabel(label);
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const QSet<QString> Collection::aliases() const
|
||||
{
|
||||
return m_aliases;
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::addAlias(QString alias)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
alias = encodePath(alias);
|
||||
|
||||
if (m_aliases.contains(alias)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
emit aliasAboutToAdd(alias);
|
||||
|
||||
bool ok = QDBusConnection::sessionBus().registerObject(
|
||||
QStringLiteral(DBUS_PATH_TEMPLATE_ALIAS).arg(p()->objectPath().path(), alias), this);
|
||||
if (ok) {
|
||||
m_aliases.insert(alias);
|
||||
emit aliasAdded(alias);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Collection::removeAlias(QString alias)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
alias = encodePath(alias);
|
||||
|
||||
if (!m_aliases.contains(alias)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QDBusConnection::sessionBus().unregisterObject(
|
||||
QStringLiteral(DBUS_PATH_TEMPLATE_ALIAS).arg(p()->objectPath().path(), alias));
|
||||
|
||||
m_aliases.remove(alias);
|
||||
emit aliasRemoved(alias);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Collection::name() const
|
||||
{
|
||||
return QFileInfo(m_backendPath).baseName();
|
||||
}
|
||||
|
||||
DatabaseWidget* Collection::backend() const
|
||||
{
|
||||
return m_backend;
|
||||
}
|
||||
|
||||
void Collection::onDatabaseLockChanged()
|
||||
{
|
||||
auto locked = backendLocked();
|
||||
if (!locked) {
|
||||
populateContents();
|
||||
} else {
|
||||
cleanupConnections();
|
||||
}
|
||||
emit collectionLockChanged(locked);
|
||||
emit collectionChanged();
|
||||
}
|
||||
|
||||
void Collection::populateContents()
|
||||
{
|
||||
if (!m_registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we have an unlocked db
|
||||
|
||||
auto newUuid = FdoSecrets::settings()->exposedGroup(m_backend->database());
|
||||
auto newGroup = m_backend->database()->rootGroup()->findGroupByUuid(newUuid);
|
||||
if (!newGroup) {
|
||||
// no exposed group, delete self
|
||||
doDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up old group connections
|
||||
cleanupConnections();
|
||||
|
||||
m_exposedGroup = newGroup;
|
||||
|
||||
// Attach signal to update exposed group settings if the group was removed.
|
||||
// The lifetime of the connection is bound to the database object, because
|
||||
// in Database::~Database, groups are also deleted, but we don't want to
|
||||
// trigger this.
|
||||
// This rely on the fact that QObject disconnects signals BEFORE deleting
|
||||
// children.
|
||||
QPointer<Database> db = m_backend->database().data();
|
||||
connect(m_exposedGroup.data(), &Group::groupAboutToRemove, db, [db](Group* toBeRemoved) {
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
auto uuid = FdoSecrets::settings()->exposedGroup(db);
|
||||
auto exposedGroup = db->rootGroup()->findGroupByUuid(uuid);
|
||||
if (toBeRemoved == exposedGroup) {
|
||||
// reset the exposed group to none
|
||||
FdoSecrets::settings()->setExposedGroup(db, {});
|
||||
}
|
||||
});
|
||||
|
||||
// Monitor exposed group settings
|
||||
connect(m_backend->database()->metadata()->customData(), &CustomData::customDataModified, this, [this]() {
|
||||
if (!m_exposedGroup || !m_backend) {
|
||||
return;
|
||||
}
|
||||
if (m_exposedGroup->uuid() == FdoSecrets::settings()->exposedGroup(m_backend->database())) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
onDatabaseExposedGroupChanged();
|
||||
});
|
||||
|
||||
// Add items for existing entry
|
||||
const auto entries = m_exposedGroup->entriesRecursive(false);
|
||||
for (const auto& entry : entries) {
|
||||
onEntryAdded(entry, false);
|
||||
}
|
||||
|
||||
connectGroupSignalRecursive(m_exposedGroup);
|
||||
}
|
||||
|
||||
void Collection::onDatabaseExposedGroupChanged()
|
||||
{
|
||||
// delete all items
|
||||
// this has to be done because the backend is actually still there
|
||||
// just we don't expose them
|
||||
for (const auto& item : asConst(m_items)) {
|
||||
item->doDelete();
|
||||
}
|
||||
|
||||
// repopulate
|
||||
Q_ASSERT(!backendLocked());
|
||||
populateContents();
|
||||
}
|
||||
|
||||
void Collection::onEntryAdded(Entry* entry, bool emitSignal)
|
||||
{
|
||||
if (inRecycleBin(entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto item = new Item(this, entry);
|
||||
m_items << item;
|
||||
m_entryToItem[entry] = item;
|
||||
|
||||
// forward delete signals
|
||||
connect(entry->group(), &Group::entryAboutToRemove, item, [item](Entry* toBeRemoved) {
|
||||
if (item->backend() == toBeRemoved) {
|
||||
item->doDelete();
|
||||
}
|
||||
});
|
||||
|
||||
// relay signals
|
||||
connect(item, &Item::itemChanged, this, [this, item]() { emit itemChanged(item); });
|
||||
connect(item, &Item::itemAboutToDelete, this, [this, item]() {
|
||||
m_items.removeAll(item);
|
||||
m_entryToItem.remove(item->backend());
|
||||
emit itemDeleted(item);
|
||||
});
|
||||
|
||||
if (emitSignal) {
|
||||
emit itemCreated(item);
|
||||
}
|
||||
}
|
||||
|
||||
void Collection::connectGroupSignalRecursive(Group* group)
|
||||
{
|
||||
if (inRecycleBin(group)) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(group, &Group::groupModified, this, &Collection::collectionChanged);
|
||||
connect(group, &Group::entryAdded, this, [this](Entry* entry) { onEntryAdded(entry, true); });
|
||||
|
||||
const auto children = group->children();
|
||||
for (const auto& cg : children) {
|
||||
connectGroupSignalRecursive(cg);
|
||||
}
|
||||
}
|
||||
|
||||
Service* Collection::service() const
|
||||
{
|
||||
return qobject_cast<Service*>(parent());
|
||||
}
|
||||
|
||||
void Collection::doLock()
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
|
||||
m_backend->lock();
|
||||
}
|
||||
|
||||
void Collection::doUnlock()
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
|
||||
service()->doUnlockDatabaseInDialog(m_backend);
|
||||
}
|
||||
|
||||
void Collection::doDelete()
|
||||
{
|
||||
emit collectionAboutToDelete();
|
||||
|
||||
unregisterCurrentPath();
|
||||
|
||||
// remove alias manually to trigger signal
|
||||
for (const auto& a : aliases()) {
|
||||
removeAlias(a).okOrDie();
|
||||
}
|
||||
|
||||
cleanupConnections();
|
||||
|
||||
m_exposedGroup = nullptr;
|
||||
|
||||
// reset backend and delete self
|
||||
m_backend = nullptr;
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void Collection::cleanupConnections()
|
||||
{
|
||||
if (m_exposedGroup) {
|
||||
m_exposedGroup->disconnect(this);
|
||||
}
|
||||
m_items.clear();
|
||||
}
|
||||
|
||||
QString Collection::backendFilePath() const
|
||||
{
|
||||
return m_backendPath;
|
||||
}
|
||||
|
||||
Group* Collection::exposedRootGroup() const
|
||||
{
|
||||
return m_exposedGroup;
|
||||
}
|
||||
|
||||
bool Collection::backendLocked() const
|
||||
{
|
||||
return !m_backend || !m_backend->database()->isInitialized() || m_backend->isLocked();
|
||||
}
|
||||
|
||||
void Collection::doDeleteEntries(QList<Entry*> entries)
|
||||
{
|
||||
m_backend->deleteEntries(std::move(entries));
|
||||
}
|
||||
|
||||
Group* Collection::findCreateGroupByPath(const QString& groupPath)
|
||||
{
|
||||
auto group = m_exposedGroup->findGroupByPath(groupPath);
|
||||
if (group) {
|
||||
return group;
|
||||
}
|
||||
|
||||
// groupPath can't be empty here, because otherwise it will match m_exposedGroup and was returned above
|
||||
Q_ASSERT(!groupPath.isEmpty());
|
||||
|
||||
auto groups = groupPath.split('/', QString::SkipEmptyParts);
|
||||
auto groupName = groups.takeLast();
|
||||
|
||||
// create parent group
|
||||
auto parentGroup = findCreateGroupByPath(groups.join('/'));
|
||||
|
||||
// create this group
|
||||
group = new Group();
|
||||
group->setUuid(QUuid::createUuid());
|
||||
group->setName(groupName);
|
||||
group->setIcon(Group::DefaultIconNumber);
|
||||
group->setParent(parentGroup);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
bool Collection::inRecycleBin(Group* group) const
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
|
||||
if (!m_backend->database()->metadata()->recycleBin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (group) {
|
||||
if (group->uuid() == m_backend->database()->metadata()->recycleBin()->uuid()) {
|
||||
return true;
|
||||
}
|
||||
group = group->parentGroup();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Collection::inRecycleBin(Entry* entry) const
|
||||
{
|
||||
Q_ASSERT(entry);
|
||||
return inRecycleBin(entry->group());
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
152
src/fdosecrets/objects/Collection.h
Normal file
152
src/fdosecrets/objects/Collection.h
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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_COLLECTION_H
|
||||
#define KEEPASSXC_FDOSECRETS_COLLECTION_H
|
||||
|
||||
#include "DBusObject.h"
|
||||
|
||||
#include "adaptors/CollectionAdaptor.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
|
||||
class Database;
|
||||
class DatabaseWidget;
|
||||
class Entry;
|
||||
class Group;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Item;
|
||||
class PromptBase;
|
||||
class Service;
|
||||
class Collection : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Collection(Service* parent, DatabaseWidget* backend);
|
||||
|
||||
DBusReturn<const QList<Item*>> items() const;
|
||||
|
||||
DBusReturn<QString> label() const;
|
||||
DBusReturn<void> setLabel(const QString& label);
|
||||
|
||||
DBusReturn<bool> locked() const;
|
||||
|
||||
DBusReturn<qulonglong> created() const;
|
||||
|
||||
DBusReturn<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);
|
||||
|
||||
signals:
|
||||
void itemCreated(const Item* item);
|
||||
void itemDeleted(const Item* item);
|
||||
void itemChanged(const Item* item);
|
||||
|
||||
void collectionChanged();
|
||||
void collectionAboutToDelete();
|
||||
void collectionLockChanged(bool newLocked);
|
||||
|
||||
void aliasAboutToAdd(const QString& alias);
|
||||
void aliasAdded(const QString& alias);
|
||||
void aliasRemoved(const QString& alias);
|
||||
|
||||
public:
|
||||
DBusReturn<void> setProperties(const QVariantMap& properties);
|
||||
|
||||
DBusReturn<void> removeAlias(QString alias);
|
||||
DBusReturn<void> addAlias(QString alias);
|
||||
const QSet<QString> aliases() const;
|
||||
|
||||
/**
|
||||
* A human readable name of the collection, available even if the db is locked
|
||||
* @return
|
||||
*/
|
||||
QString name() const;
|
||||
|
||||
Group* exposedRootGroup() const;
|
||||
DatabaseWidget* backend() const;
|
||||
QString backendFilePath() const;
|
||||
Service* service() const;
|
||||
bool inRecycleBin(Group* group) const;
|
||||
bool inRecycleBin(Entry* entry) const;
|
||||
|
||||
public slots:
|
||||
// expose some methods for Prmopt to use
|
||||
void doLock();
|
||||
void doUnlock();
|
||||
// will remove self
|
||||
void doDelete();
|
||||
|
||||
// delete the Entry in backend from this collection
|
||||
void doDeleteEntries(QList<Entry*> entries);
|
||||
|
||||
private slots:
|
||||
void onDatabaseLockChanged();
|
||||
void onDatabaseExposedGroupChanged();
|
||||
void reloadBackend();
|
||||
|
||||
private:
|
||||
friend class DeleteCollectionPrompt;
|
||||
friend class CreateCollectionPrompt;
|
||||
|
||||
void onEntryAdded(Entry* entry, bool emitSignal);
|
||||
void populateContents();
|
||||
void connectGroupSignalRecursive(Group* group);
|
||||
void cleanupConnections();
|
||||
|
||||
bool backendLocked() const;
|
||||
|
||||
/**
|
||||
* Check if the backend is a valid object, send error reply if not.
|
||||
* @return true if the backend is valid.
|
||||
*/
|
||||
DBusReturn<void> ensureBackend() const;
|
||||
|
||||
/**
|
||||
* Ensure the database is unlocked, send error reply if locked.
|
||||
* @return true if the database is locked
|
||||
*/
|
||||
DBusReturn<void> ensureUnlocked() const;
|
||||
|
||||
/**
|
||||
* Like mkdir -p, find or create the group by path, under m_exposedGroup
|
||||
* @param groupPath
|
||||
* @return created or found group
|
||||
*/
|
||||
Group* findCreateGroupByPath(const QString& groupPath);
|
||||
|
||||
private:
|
||||
QPointer<DatabaseWidget> m_backend;
|
||||
QString m_backendPath;
|
||||
QPointer<Group> m_exposedGroup;
|
||||
|
||||
QSet<QString> m_aliases;
|
||||
QList<Item*> m_items;
|
||||
QMap<const Entry*, Item*> m_entryToItem;
|
||||
|
||||
bool m_registered;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_COLLECTION_H
|
64
src/fdosecrets/objects/DBusObject.cpp
Normal file
64
src/fdosecrets/objects/DBusObject.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 "DBusObject.h"
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
DBusObject::DBusObject(DBusObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DBusObject::registerWithPath(const QString& path, QDBusAbstractAdaptor* adaptor)
|
||||
{
|
||||
m_objectPath.setPath(path);
|
||||
m_dbusAdaptor = adaptor;
|
||||
adaptor->setParent(this);
|
||||
auto ok = QDBusConnection::sessionBus().registerObject(m_objectPath.path(), this);
|
||||
Q_UNUSED(ok);
|
||||
Q_ASSERT(ok);
|
||||
}
|
||||
|
||||
QString DBusObject::callingPeerName() const
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
QString encodePath(const QString& value)
|
||||
{
|
||||
// force "-.~_" to be encoded
|
||||
return QUrl::toPercentEncoding(value, "", "-.~_").replace('%', '_');
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
178
src/fdosecrets/objects/DBusObject.h
Normal file
178
src/fdosecrets/objects/DBusObject.h
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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 <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusContext>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMetaProperty>
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class QDBusAbstractAdaptor;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class Service;
|
||||
|
||||
/**
|
||||
* @brief A common base class for all dbus-exposed objects
|
||||
*/
|
||||
class DBusObject : public QObject, public QDBusContext
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DBusObject(DBusObject* parent = nullptr);
|
||||
|
||||
const QDBusObjectPath& objectPath() const
|
||||
{
|
||||
return m_objectPath;
|
||||
}
|
||||
|
||||
protected:
|
||||
void registerWithPath(const QString& path, QDBusAbstractAdaptor* adaptor);
|
||||
|
||||
void unregisterCurrentPath()
|
||||
{
|
||||
QDBusConnection::sessionBus().unregisterObject(m_objectPath.path());
|
||||
m_dbusAdaptor = nullptr;
|
||||
m_objectPath.setPath(QStringLiteral("/"));
|
||||
}
|
||||
|
||||
QString callingPeer() const
|
||||
{
|
||||
Q_ASSERT(calledFromDBus());
|
||||
return message().service();
|
||||
}
|
||||
|
||||
uint callingPeerPid() const
|
||||
{
|
||||
return connection().interface()->servicePid(callingPeer());
|
||||
}
|
||||
|
||||
QString callingPeerName() const;
|
||||
|
||||
template <typename Adaptor> Adaptor& dbusAdaptor() const
|
||||
{
|
||||
return *static_cast<Adaptor*>(m_dbusAdaptor);
|
||||
}
|
||||
|
||||
DBusObject* p() const
|
||||
{
|
||||
return qobject_cast<DBusObject*>(parent());
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Derived class should not directly use sendErrorReply.
|
||||
* Instead, use raiseError
|
||||
*/
|
||||
using QDBusContext::sendErrorReply;
|
||||
|
||||
template <typename U> friend class DBusReturn;
|
||||
|
||||
private:
|
||||
QDBusAbstractAdaptor* m_dbusAdaptor;
|
||||
QDBusObjectPath m_objectPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
18
src/fdosecrets/objects/DBusReturn.cpp
Normal file
18
src/fdosecrets/objects/DBusReturn.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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"
|
246
src/fdosecrets/objects/DBusReturn.h
Normal file
246
src/fdosecrets/objects/DBusReturn.h
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
template <typename P> T valueOrHandle(P* p) const&
|
||||
{
|
||||
if (isError()) {
|
||||
if (p->calledFromDBus()) {
|
||||
p->sendErrorReply(errorName());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return m_value;
|
||||
}
|
||||
|
||||
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
|
43
src/fdosecrets/objects/DBusTypes.cpp
Normal file
43
src/fdosecrets/objects/DBusTypes.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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>();
|
||||
|
||||
// NOTE: this is already registered by Qt in qtextratypes.h
|
||||
// qRegisterMetaType<QList<QDBusObjectPath > >();
|
||||
// qDBusRegisterMetaType<QList<QDBusObjectPath> >();
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
91
src/fdosecrets/objects/DBusTypes.h
Normal file
91
src/fdosecrets/objects/DBusTypes.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
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
|
420
src/fdosecrets/objects/Item.cpp
Normal file
420
src/fdosecrets/objects/Item.cpp
Normal file
@ -0,0 +1,420 @@
|
||||
/*
|
||||
* 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 "Item.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
#include <QMimeDatabase>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QTextCodec>
|
||||
|
||||
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);
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto FDO_SECRETS_DATA = "FDO_SECRETS_DATA";
|
||||
constexpr auto FDO_SECRETS_CONTENT_TYPE = "FDO_SECRETS_CONTENT_TYPE";
|
||||
} // namespace
|
||||
|
||||
Item::Item(Collection* parent, Entry* backend)
|
||||
: DBusObject(parent)
|
||||
, m_backend(backend)
|
||||
{
|
||||
Q_ASSERT(!p()->objectPath().path().isEmpty());
|
||||
|
||||
registerWithPath(QStringLiteral(DBUS_PATH_TEMPLATE_ITEM).arg(p()->objectPath().path(), m_backend->uuidToHex()),
|
||||
new ItemAdaptor(this));
|
||||
|
||||
connect(m_backend.data(), &Entry::entryModified, this, &Item::itemChanged);
|
||||
}
|
||||
|
||||
DBusReturn<bool> Item::locked() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
return collection()->locked();
|
||||
}
|
||||
|
||||
DBusReturn<const StringStringMap> Item::attributes() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
StringStringMap attrs;
|
||||
|
||||
// add default attributes except password
|
||||
auto entryAttrs = m_backend->attributes();
|
||||
for (const auto& attr : EntryAttributes::DefaultAttributes) {
|
||||
if (entryAttrs->isProtected(attr) || attr == EntryAttributes::PasswordKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value = entryAttrs->value(attr);
|
||||
if (entryAttrs->isReference(attr)) {
|
||||
value = m_backend->maskPasswordPlaceholders(value);
|
||||
value = m_backend->resolveMultiplePlaceholders(value);
|
||||
}
|
||||
attrs[attr] = value;
|
||||
}
|
||||
|
||||
// add custom attributes
|
||||
const auto customKeys = entryAttrs->customKeys();
|
||||
for (const auto& attr : customKeys) {
|
||||
// decode attr key
|
||||
auto decoded = decodeAttributeKey(attr);
|
||||
|
||||
attrs[decoded] = entryAttrs->value(attr);
|
||||
}
|
||||
|
||||
// add some informative and readonly attributes
|
||||
attrs[ItemAttributes::UuidKey] = m_backend->uuidToHex();
|
||||
attrs[ItemAttributes::PathKey] = path();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setAttributes(const StringStringMap& attrs)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
m_backend->beginUpdate();
|
||||
|
||||
auto entryAttrs = m_backend->attributes();
|
||||
for (auto it = attrs.constBegin(); it != attrs.constEnd(); ++it) {
|
||||
if (entryAttrs->isProtected(it.key()) || it.key() == EntryAttributes::PasswordKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ReadOnlyAttributes.contains(it.key())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto encoded = encodeAttributeKey(it.key());
|
||||
entryAttrs->set(encoded, it.value());
|
||||
}
|
||||
|
||||
m_backend->endUpdate();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<QString> Item::label() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return m_backend->title();
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setLabel(const QString& label)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
m_backend->beginUpdate();
|
||||
m_backend->setTitle(label);
|
||||
m_backend->endUpdate();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Item::created() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return static_cast<qulonglong>(m_backend->timeInfo().creationTime().toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
DBusReturn<qulonglong> Item::modified() const
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return static_cast<qulonglong>(m_backend->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
DBusReturn<PromptBase*> Item::deleteItem()
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
auto prompt = new DeleteItemPrompt(service(), this);
|
||||
return prompt;
|
||||
}
|
||||
|
||||
DBusReturn<SecretStruct> Item::getSecret(Session* session)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
}
|
||||
|
||||
auto 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;
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setSecret(const SecretStruct& secret)
|
||||
{
|
||||
auto ret = ensureBackend();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
ret = ensureUnlocked();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto session = pathToObject<Session>(secret.session);
|
||||
if (!session) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
}
|
||||
|
||||
// decode using session
|
||||
auto decoded = session->decode(secret);
|
||||
|
||||
// set in backend
|
||||
m_backend->beginUpdate();
|
||||
setEntrySecret(m_backend, decoded.value, decoded.contentType);
|
||||
m_backend->endUpdate();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::setProperties(const QVariantMap& properties)
|
||||
{
|
||||
auto label = properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Label")).toString();
|
||||
|
||||
auto ret = setLabel(label);
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto attributes = qdbus_cast<StringStringMap>(
|
||||
properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes")).value<QDBusArgument>());
|
||||
ret = setAttributes(attributes);
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Collection* Item::collection() const
|
||||
{
|
||||
return qobject_cast<Collection*>(p());
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::ensureBackend() const
|
||||
{
|
||||
if (!m_backend) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DBusReturn<void> Item::ensureUnlocked() const
|
||||
{
|
||||
auto locked = collection()->locked();
|
||||
if (locked.isError()) {
|
||||
return locked;
|
||||
}
|
||||
if (locked.value()) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_IS_LOCKED));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Entry* Item::backend() const
|
||||
{
|
||||
return m_backend;
|
||||
}
|
||||
|
||||
void Item::doDelete()
|
||||
{
|
||||
emit itemAboutToDelete();
|
||||
|
||||
// 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.
|
||||
unregisterCurrentPath();
|
||||
|
||||
m_backend = nullptr;
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
Service* Item::service() const
|
||||
{
|
||||
return collection()->service();
|
||||
}
|
||||
|
||||
QString Item::path() const
|
||||
{
|
||||
QStringList pathComponents{m_backend->title()};
|
||||
|
||||
Group* group = m_backend->group();
|
||||
while (group && group != collection()->exposedRootGroup()) {
|
||||
pathComponents.prepend(group->name());
|
||||
group = group->parentGroup();
|
||||
}
|
||||
// we should always reach the exposed root group
|
||||
Q_ASSERT(group);
|
||||
|
||||
// root group is represented by a single slash, thus adding an empty component.
|
||||
pathComponents.prepend(QLatin1Literal(""));
|
||||
|
||||
return pathComponents.join('/');
|
||||
}
|
||||
|
||||
QString Item::encodeAttributeKey(const QString &key)
|
||||
{
|
||||
return QUrl::toPercentEncoding(key, "", "_:").replace('%', '_');
|
||||
}
|
||||
|
||||
QString Item::decodeAttributeKey(const QString &key)
|
||||
{
|
||||
return QString::fromUtf8(QByteArray::fromPercentEncoding(key.toLatin1(), '_'));
|
||||
}
|
||||
|
||||
void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType)
|
||||
{
|
||||
auto mimeName = contentType.split(';').takeFirst().trimmed();
|
||||
|
||||
// find the mime type
|
||||
QMimeDatabase db;
|
||||
auto mimeType = db.mimeTypeForName(mimeName);
|
||||
|
||||
// find a suitable codec
|
||||
QTextCodec* codec = nullptr;
|
||||
static const QRegularExpression charsetPattern(QStringLiteral(R"re(charset=(?<encode>.+)$)re"));
|
||||
auto match = charsetPattern.match(contentType);
|
||||
if (match.hasMatch()) {
|
||||
codec = QTextCodec::codecForName(match.captured(QStringLiteral("encode")).toLatin1());
|
||||
} else {
|
||||
codec = QTextCodec::codecForName(QByteArrayLiteral("utf-8"));
|
||||
}
|
||||
|
||||
if (!mimeType.isValid() || !mimeType.inherits(QStringLiteral("text/plain")) || !codec) {
|
||||
// we can't handle this content type, save the data as attachment
|
||||
entry->attachments()->set(FDO_SECRETS_DATA, data);
|
||||
entry->attributes()->set(FDO_SECRETS_CONTENT_TYPE, contentType);
|
||||
return;
|
||||
}
|
||||
|
||||
// save the data to password field
|
||||
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
||||
entry->attachments()->remove(FDO_SECRETS_DATA);
|
||||
}
|
||||
if (entry->attributes()->hasKey(FDO_SECRETS_CONTENT_TYPE)) {
|
||||
entry->attributes()->remove(FDO_SECRETS_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
Q_ASSERT(codec);
|
||||
entry->setPassword(codec->toUnicode(data));
|
||||
}
|
||||
|
||||
SecretStruct getEntrySecret(Entry* entry)
|
||||
{
|
||||
SecretStruct ss;
|
||||
|
||||
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
||||
ss.value = entry->attachments()->value(FDO_SECRETS_DATA);
|
||||
Q_ASSERT(entry->attributes()->hasKey(FDO_SECRETS_CONTENT_TYPE));
|
||||
ss.contentType = entry->attributes()->value(FDO_SECRETS_CONTENT_TYPE);
|
||||
return ss;
|
||||
}
|
||||
|
||||
ss.value = entry->resolveMultiplePlaceholders(entry->password()).toUtf8();
|
||||
ss.contentType = QStringLiteral("text/plain");
|
||||
return ss;
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
113
src/fdosecrets/objects/Item.h
Normal file
113
src/fdosecrets/objects/Item.h
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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_ITEM_H
|
||||
#define KEEPASSXC_FDOSECRETS_ITEM_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/adaptors/ItemAdaptor.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
class Entry;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
namespace ItemAttributes
|
||||
{
|
||||
constexpr const auto UuidKey = "Uuid";
|
||||
constexpr const auto PathKey = "Path";
|
||||
} // namespace ItemAttributes
|
||||
|
||||
class Session;
|
||||
class Collection;
|
||||
class PromptBase;
|
||||
|
||||
class Item : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Item(Collection* parent, Entry* backend);
|
||||
|
||||
DBusReturn<bool> locked() const;
|
||||
|
||||
DBusReturn<const StringStringMap> attributes() const;
|
||||
DBusReturn<void> setAttributes(const StringStringMap& attrs);
|
||||
|
||||
DBusReturn<QString> label() const;
|
||||
DBusReturn<void> setLabel(const QString& label);
|
||||
|
||||
DBusReturn<qulonglong> created() const;
|
||||
|
||||
DBusReturn<qulonglong> modified() const;
|
||||
|
||||
DBusReturn<PromptBase*> deleteItem();
|
||||
DBusReturn<SecretStruct> getSecret(Session* session);
|
||||
DBusReturn<void> setSecret(const SecretStruct& secret);
|
||||
|
||||
signals:
|
||||
void itemChanged();
|
||||
void itemAboutToDelete();
|
||||
|
||||
public:
|
||||
static const QSet<QString> ReadOnlyAttributes;
|
||||
|
||||
/**
|
||||
* Due to the limitation in EntrySearcher, custom attr key cannot contain ':',
|
||||
* Thus we encode the key when saving and decode it when returning.
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
static QString encodeAttributeKey(const QString& key);
|
||||
static QString decodeAttributeKey(const QString& key);
|
||||
|
||||
DBusReturn<void> setProperties(const QVariantMap& properties);
|
||||
|
||||
Entry* backend() const;
|
||||
Collection* collection() const;
|
||||
Service* service() const;
|
||||
|
||||
/**
|
||||
* Compute the entry path relative to the exposed group
|
||||
* @return the entry path
|
||||
*/
|
||||
QString path() const;
|
||||
|
||||
public slots:
|
||||
void doDelete();
|
||||
|
||||
private:
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Ensure the database is unlocked, send error reply if locked.
|
||||
* @return true if the database is locked
|
||||
*/
|
||||
DBusReturn<void> ensureUnlocked() const;
|
||||
|
||||
private:
|
||||
QPointer<Entry> m_backend;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_ITEM_H
|
238
src/fdosecrets/objects/Prompt.cpp
Normal file
238
src/fdosecrets/objects/Prompt.cpp
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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 "Prompt.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
|
||||
#include "core/Tools.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QWindow>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
PromptBase::PromptBase(Service* parent)
|
||||
: DBusObject(parent)
|
||||
{
|
||||
registerWithPath(
|
||||
QStringLiteral("%1/prompt/%2").arg(p()->objectPath().path(), Tools::uuidToHex(QUuid::createUuid())),
|
||||
new PromptAdaptor(this));
|
||||
|
||||
connect(this, &PromptBase::completed, this, &PromptBase::deleteLater);
|
||||
}
|
||||
|
||||
QWindow* PromptBase::findWindow(const QString& windowId)
|
||||
{
|
||||
// find parent window, or nullptr if not found
|
||||
bool ok = false;
|
||||
WId wid = windowId.toULongLong(&ok, 0);
|
||||
QWindow* parent = nullptr;
|
||||
if (ok) {
|
||||
parent = QWindow::fromWinId(wid);
|
||||
}
|
||||
if (parent) {
|
||||
// parent is not the child of any object, so make sure it gets deleted at some point
|
||||
QObject::connect(this, &QObject::destroyed, parent, &QObject::deleteLater);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
Service* PromptBase::service() const
|
||||
{
|
||||
return qobject_cast<Service*>(parent());
|
||||
}
|
||||
|
||||
DBusReturn<void> PromptBase::dismiss()
|
||||
{
|
||||
emit completed(true, {});
|
||||
return {};
|
||||
}
|
||||
|
||||
DeleteCollectionPrompt::DeleteCollectionPrompt(Service* parent, Collection* coll)
|
||||
: PromptBase(parent)
|
||||
, m_collection(coll)
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn<void> DeleteCollectionPrompt::prompt(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));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!m_collection) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
// only need to delete in backend, collection will react itself.
|
||||
service()->doCloseDatabase(m_collection->backend());
|
||||
|
||||
emit completed(false, {});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CreateCollectionPrompt::CreateCollectionPrompt(Service* parent)
|
||||
: PromptBase(parent)
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn<void> CreateCollectionPrompt::prompt(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));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
|
||||
auto coll = service()->doNewDatabase();
|
||||
if (!coll) {
|
||||
return dismiss();
|
||||
}
|
||||
|
||||
emit collectionCreated(coll);
|
||||
emit completed(false, coll->objectPath().path());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
LockCollectionsPrompt::LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
||||
: PromptBase(parent)
|
||||
{
|
||||
m_collections.reserve(colls.size());
|
||||
for (const auto& c : asConst(colls)) {
|
||||
m_collections << c;
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<void> LockCollectionsPrompt::prompt(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));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
|
||||
QList<QDBusObjectPath> locked;
|
||||
for (const auto& c : asConst(m_collections)) {
|
||||
if (c) {
|
||||
c->doLock();
|
||||
locked << c->objectPath();
|
||||
}
|
||||
}
|
||||
|
||||
emit completed(false, QVariant::fromValue(locked));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
UnlockCollectionsPrompt::UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
||||
: PromptBase(parent)
|
||||
{
|
||||
m_collections.reserve(colls.size());
|
||||
for (const auto& c : asConst(colls)) {
|
||||
m_collections << c;
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<void> UnlockCollectionsPrompt::prompt(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));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
|
||||
QList<QDBusObjectPath> unlocked;
|
||||
for (const auto& c : asConst(m_collections)) {
|
||||
if (c) {
|
||||
c->doUnlock();
|
||||
unlocked << c->objectPath();
|
||||
}
|
||||
}
|
||||
|
||||
emit completed(false, QVariant::fromValue(unlocked));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item)
|
||||
: PromptBase(parent)
|
||||
, m_item(item)
|
||||
{
|
||||
}
|
||||
|
||||
DBusReturn<void> DeleteItemPrompt::prompt(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));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBox::OverrideParent override(findWindow(windowId));
|
||||
|
||||
// delete item's backend. Item will be notified after the backend is deleted.
|
||||
if (m_item) {
|
||||
if (FdoSecrets::settings()->noConfirmDeleteItem()) {
|
||||
MessageBox::setNextAnswer(MessageBox::Move);
|
||||
}
|
||||
m_item->collection()->doDeleteEntries({m_item->backend()});
|
||||
}
|
||||
|
||||
emit completed(false, {});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
121
src/fdosecrets/objects/Prompt.h
Normal file
121
src/fdosecrets/objects/Prompt.h
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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_PROMPT_H
|
||||
#define KEEPASSXC_FDOSECRETS_PROMPT_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/adaptors/PromptAdaptor.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
class QWindow;
|
||||
|
||||
class DatabaseWidget;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Service;
|
||||
|
||||
class PromptBase : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PromptBase(Service* parent);
|
||||
|
||||
virtual DBusReturn<void> prompt(const QString& windowId) = 0;
|
||||
|
||||
virtual DBusReturn<void> dismiss();
|
||||
|
||||
signals:
|
||||
void completed(bool dismissed, const QVariant& result);
|
||||
|
||||
protected:
|
||||
QWindow* findWindow(const QString& windowId);
|
||||
Service* service() const;
|
||||
};
|
||||
|
||||
class Collection;
|
||||
|
||||
class DeleteCollectionPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeleteCollectionPrompt(Service* parent, Collection* coll);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
|
||||
private:
|
||||
QPointer<Collection> m_collection;
|
||||
};
|
||||
|
||||
class CreateCollectionPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CreateCollectionPrompt(Service* parent);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
|
||||
signals:
|
||||
void collectionCreated(Collection* coll);
|
||||
};
|
||||
|
||||
class LockCollectionsPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
|
||||
private:
|
||||
QList<QPointer<Collection>> m_collections;
|
||||
};
|
||||
|
||||
class UnlockCollectionsPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& coll);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
|
||||
private:
|
||||
QList<QPointer<Collection>> m_collections;
|
||||
};
|
||||
|
||||
class Item;
|
||||
class DeleteItemPrompt : public PromptBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeleteItemPrompt(Service* parent, Item* item);
|
||||
|
||||
DBusReturn<void> prompt(const QString& windowId) override;
|
||||
|
||||
private:
|
||||
QPointer<Item> m_item;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_PROMPT_H
|
477
src/fdosecrets/objects/Service.cpp
Normal file
477
src/fdosecrets/objects/Service.cpp
Normal file
@ -0,0 +1,477 @@
|
||||
/*
|
||||
* 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 "Service.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Item.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QDebug>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto DEFAULT_ALIAS = "default";
|
||||
}
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
Service::Service(FdoSecretsPlugin* plugin,
|
||||
QPointer<DatabaseTabWidget> dbTabs) // clazy: exclude=ctor-missing-parent-argument
|
||||
: DBusObject(nullptr)
|
||||
, m_plugin(plugin)
|
||||
, m_databases(std::move(dbTabs))
|
||||
, m_insdieEnsureDefaultAlias(false)
|
||||
, m_serviceWatcher(nullptr)
|
||||
{
|
||||
registerWithPath(QStringLiteral(DBUS_PATH_SECRETS), new ServiceAdaptor(this));
|
||||
}
|
||||
|
||||
Service::~Service()
|
||||
{
|
||||
QDBusConnection::sessionBus().unregisterService(QStringLiteral(DBUS_SERVICE_SECRET));
|
||||
}
|
||||
|
||||
bool Service::initialize()
|
||||
{
|
||||
if (!QDBusConnection::sessionBus().registerService(QStringLiteral(DBUS_SERVICE_SECRET))) {
|
||||
qDebug() << "Another secret service is running";
|
||||
emit error(tr("Failed to register DBus service at %1: another secret service is running.")
|
||||
.arg(QLatin1Literal(DBUS_SERVICE_SECRET)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connect to service unregistered signal
|
||||
m_serviceWatcher.reset(new QDBusServiceWatcher());
|
||||
connect(
|
||||
m_serviceWatcher.data(), &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);
|
||||
onDatabaseTabOpened(dbWidget, false);
|
||||
}
|
||||
|
||||
// Connect to new database signal
|
||||
// No need to connect to close signal, as collection will remove itself when backend delete/close database tab.
|
||||
connect(m_databases.data(), &DatabaseTabWidget::databaseOpened, this, [this](DatabaseWidget* dbWidget) {
|
||||
onDatabaseTabOpened(dbWidget, true);
|
||||
});
|
||||
|
||||
// make default alias track current activated database
|
||||
connect(m_databases.data(), &DatabaseTabWidget::activateDatabaseChanged, this, &Service::ensureDefaultAlias);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Service::onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal)
|
||||
{
|
||||
auto coll = new Collection(this, dbWidget);
|
||||
|
||||
m_collections << coll;
|
||||
m_dbToCollection[dbWidget] = coll;
|
||||
|
||||
// handle alias
|
||||
connect(coll, &Collection::aliasAboutToAdd, this, &Service::onCollectionAliasAboutToAdd);
|
||||
connect(coll, &Collection::aliasAdded, this, &Service::onCollectionAliasAdded);
|
||||
connect(coll, &Collection::aliasRemoved, this, &Service::onCollectionAliasRemoved);
|
||||
|
||||
ensureDefaultAlias();
|
||||
|
||||
// Forward delete signal, we have to rely on filepath to identify the database being closed,
|
||||
// but we can not access m_backend safely because during the databaseClosed signal,
|
||||
// m_backend may already be reset to nullptr
|
||||
// We want to remove the collection object from dbus as early as possible, to avoid
|
||||
// race conditions when deleteLater was called on the m_backend, but not delivered yet,
|
||||
// and new method calls from dbus occurred. Therefore we can't rely on the destroyed
|
||||
// signal on m_backend.
|
||||
// bind to coll lifespan
|
||||
connect(m_databases.data(), &DatabaseTabWidget::databaseClosed, coll, [coll](const QString& filePath) {
|
||||
if (filePath == coll->backendFilePath()) {
|
||||
coll->doDelete();
|
||||
}
|
||||
});
|
||||
|
||||
// relay signals
|
||||
connect(coll, &Collection::collectionChanged, this, [this, coll]() { emit collectionChanged(coll); });
|
||||
connect(coll, &Collection::collectionAboutToDelete, this, [this, coll]() {
|
||||
m_collections.removeAll(coll);
|
||||
m_dbToCollection.remove(coll->backend());
|
||||
emit collectionDeleted(coll);
|
||||
});
|
||||
|
||||
// a special case: the database changed from no expose to expose something.
|
||||
// in this case, there is no collection out there monitoring it, so create a new collection
|
||||
if (!dbWidget->isLocked()) {
|
||||
monitorDatabaseExposedGroup(dbWidget);
|
||||
}
|
||||
connect(dbWidget, &DatabaseWidget::databaseUnlocked, this, [this, dbWidget]() {
|
||||
monitorDatabaseExposedGroup(dbWidget);
|
||||
});
|
||||
|
||||
if (emitSignal) {
|
||||
emit collectionCreated(coll);
|
||||
}
|
||||
}
|
||||
|
||||
void Service::monitorDatabaseExposedGroup(DatabaseWidget* dbWidget)
|
||||
{
|
||||
Q_ASSERT(dbWidget);
|
||||
connect(
|
||||
dbWidget->database()->metadata()->customData(), &CustomData::customDataModified, this, [this, dbWidget]() {
|
||||
if (!FdoSecrets::settings()->exposedGroup(dbWidget->database()).isNull() && !findCollection(dbWidget)) {
|
||||
onDatabaseTabOpened(dbWidget, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Service::ensureDefaultAlias()
|
||||
{
|
||||
if (m_insdieEnsureDefaultAlias) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_insdieEnsureDefaultAlias = true;
|
||||
|
||||
auto coll = findCollection(m_databases->currentDatabaseWidget());
|
||||
if (coll) {
|
||||
// adding alias will automatically remove the association with previous collection.
|
||||
coll->addAlias(DEFAULT_ALIAS).okOrDie();
|
||||
}
|
||||
|
||||
m_insdieEnsureDefaultAlias = false;
|
||||
}
|
||||
|
||||
void Service::dbusServiceUnregistered(const QString& service)
|
||||
{
|
||||
Q_ASSERT(m_serviceWatcher);
|
||||
|
||||
auto removed = m_serviceWatcher->removeWatchedService(service);
|
||||
Q_UNUSED(removed);
|
||||
Q_ASSERT(removed);
|
||||
|
||||
Session::CleanupNegotiation(service);
|
||||
auto sess = m_peerToSession.value(service, nullptr);
|
||||
if (sess) {
|
||||
sess->close().okOrDie();
|
||||
}
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Collection*>> Service::collections() const
|
||||
{
|
||||
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);
|
||||
if (incomplete) {
|
||||
result = nullptr;
|
||||
return output;
|
||||
}
|
||||
if (!ciphers) {
|
||||
return DBusReturn<>::Error(QDBusError::NotSupported);
|
||||
}
|
||||
result = new Session(std::move(ciphers), callingPeerName(), this);
|
||||
|
||||
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);
|
||||
});
|
||||
emit sessionOpened(result);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
DBusReturn<Collection*>
|
||||
Service::createCollection(const QVariantMap& properties, const QString& alias, PromptBase*& prompt)
|
||||
{
|
||||
prompt = nullptr;
|
||||
|
||||
// return existing collection if alias is non-empty and exists.
|
||||
auto collection = findCollection(alias);
|
||||
if (!collection) {
|
||||
auto cp = new CreateCollectionPrompt(this);
|
||||
prompt = cp;
|
||||
|
||||
// collection will be created when the prompt complets.
|
||||
// once it's done, we set additional properties on the collection
|
||||
connect(cp, &CreateCollectionPrompt::collectionCreated, cp, [alias, properties](Collection* coll) {
|
||||
coll->setProperties(properties).okOrDie();
|
||||
if (!alias.isEmpty()) {
|
||||
coll->addAlias(alias).okOrDie();
|
||||
}
|
||||
});
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
DBusReturn<const QList<Item*>> Service::searchItems(const StringStringMap& attributes, QList<Item*>& locked)
|
||||
{
|
||||
auto ret = collections();
|
||||
if (ret.isError()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<Item*> unlocked;
|
||||
for (const auto& coll : ret.value()) {
|
||||
auto items = coll->searchItems(attributes);
|
||||
if (items.isError()) {
|
||||
return items;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
prompt = new UnlockCollectionsPrompt(this, toUnlock);
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> Service::lock(const QList<DBusObject*>& objects, PromptBase*& prompt)
|
||||
{
|
||||
QSet<Collection*> needLock;
|
||||
needLock.reserve(objects.size());
|
||||
for (const auto& obj : asConst(objects)) {
|
||||
auto coll = qobject_cast<Collection*>(obj);
|
||||
if (coll) {
|
||||
needLock << coll;
|
||||
} else {
|
||||
auto item = qobject_cast<Item*>(obj);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
// we lock the whole collection for item
|
||||
needLock << item->collection();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (l.value()) {
|
||||
locked << coll;
|
||||
} else {
|
||||
toLock << coll;
|
||||
}
|
||||
}
|
||||
prompt = new LockCollectionsPrompt(this, toLock);
|
||||
return locked;
|
||||
}
|
||||
|
||||
DBusReturn<const QHash<Item*, SecretStruct>> Service::getSecrets(const QList<Item*>& items, Session* session)
|
||||
{
|
||||
if (!session) {
|
||||
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||
}
|
||||
|
||||
QHash<Item*, SecretStruct> res;
|
||||
|
||||
for (const auto& item : asConst(items)) {
|
||||
auto ret = item->getSecret(session);
|
||||
if (ret.isError()) {
|
||||
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;
|
||||
}
|
||||
|
||||
DBusReturn<Collection*> Service::readAlias(const QString& name)
|
||||
{
|
||||
return findCollection(name);
|
||||
}
|
||||
|
||||
DBusReturn<void> 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 collection->removeAlias(name);
|
||||
}
|
||||
return collection->addAlias(name);
|
||||
}
|
||||
|
||||
Collection* Service::findCollection(const QString& alias) const
|
||||
{
|
||||
if (alias.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = m_aliases.find(alias);
|
||||
if (it != m_aliases.end()) {
|
||||
return it.value();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Service::onCollectionAliasAboutToAdd(const QString& alias)
|
||||
{
|
||||
auto coll = qobject_cast<Collection*>(sender());
|
||||
|
||||
auto it = m_aliases.constFind(alias);
|
||||
if (it != m_aliases.constEnd() && it.value() != coll) {
|
||||
// another collection holds the alias
|
||||
// remove it first
|
||||
it.value()->removeAlias(alias).okOrDie();
|
||||
|
||||
// onCollectionAliasRemoved called through signal
|
||||
// `it` becomes invalidated now
|
||||
}
|
||||
}
|
||||
|
||||
void Service::onCollectionAliasAdded(const QString& alias)
|
||||
{
|
||||
auto coll = qobject_cast<Collection*>(sender());
|
||||
m_aliases[alias] = coll;
|
||||
}
|
||||
|
||||
void Service::onCollectionAliasRemoved(const QString& alias)
|
||||
{
|
||||
m_aliases.remove(alias);
|
||||
ensureDefaultAlias();
|
||||
}
|
||||
|
||||
Collection* Service::findCollection(const DatabaseWidget* db) const
|
||||
{
|
||||
return m_dbToCollection.value(db, nullptr);
|
||||
}
|
||||
|
||||
const QList<Session*> Service::sessions() const
|
||||
{
|
||||
return m_sessions;
|
||||
}
|
||||
|
||||
void Service::doCloseDatabase(DatabaseWidget* dbWidget)
|
||||
{
|
||||
m_databases->closeDatabaseTab(dbWidget);
|
||||
}
|
||||
|
||||
Collection* Service::doNewDatabase()
|
||||
{
|
||||
auto dbWidget = m_databases->newDatabase();
|
||||
if (!dbWidget) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// database created through dbus will be exposed to dbus by default
|
||||
auto db = dbWidget->database();
|
||||
FdoSecrets::settings()->setExposedGroup(db, db->rootGroup()->uuid());
|
||||
|
||||
auto collection = findCollection(dbWidget);
|
||||
|
||||
Q_ASSERT(collection);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
void Service::doSwitchToChangeDatabaseSettings(DatabaseWidget* dbWidget)
|
||||
{
|
||||
// switch selected to current
|
||||
// unlock if needed
|
||||
if (dbWidget->isLocked()) {
|
||||
m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None);
|
||||
}
|
||||
m_databases->setCurrentWidget(dbWidget);
|
||||
m_databases->changeDatabaseSettings();
|
||||
|
||||
// open settings (switch from app settings to m_dbTabs)
|
||||
m_plugin->emitRequestSwitchToDatabases();
|
||||
}
|
||||
|
||||
void Service::doUnlockDatabaseInDialog(DatabaseWidget* dbWidget)
|
||||
{
|
||||
m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None);
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
154
src/fdosecrets/objects/Service.h
Normal file
154
src/fdosecrets/objects/Service.h
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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_SERVICE_H
|
||||
#define KEEPASSXC_FDOSECRETS_SERVICE_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/adaptors/ServiceAdaptor.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
#include <QVariant>
|
||||
|
||||
class QDBusServiceWatcher;
|
||||
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
class Group;
|
||||
|
||||
class FdoSecretsPlugin;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Collection;
|
||||
class Item;
|
||||
class PromptBase;
|
||||
class ServiceAdaptor;
|
||||
class Session;
|
||||
|
||||
class Service : public DBusObject // clazy: exclude=ctor-missing-parent-argument
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Service(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs);
|
||||
~Service() override;
|
||||
|
||||
bool initialize();
|
||||
|
||||
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);
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> unlock(const QList<DBusObject*>& objects, PromptBase*& prompt);
|
||||
|
||||
DBusReturn<const QList<DBusObject*>> lock(const QList<DBusObject*>& objects, PromptBase*& prompt);
|
||||
|
||||
DBusReturn<const QHash<Item*, SecretStruct>> getSecrets(const QList<Item*>& items, Session* session);
|
||||
|
||||
DBusReturn<Collection*> readAlias(const QString& name);
|
||||
|
||||
DBusReturn<void> setAlias(const QString& name, Collection* collection);
|
||||
|
||||
/**
|
||||
* List of collections
|
||||
* @return
|
||||
*/
|
||||
DBusReturn<const 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);
|
||||
|
||||
/**
|
||||
* Report error message to the GUI
|
||||
* @param msg
|
||||
*/
|
||||
void error(const QString& msg);
|
||||
|
||||
public:
|
||||
/**
|
||||
* List of sessions
|
||||
* @return
|
||||
*/
|
||||
const QList<Session*> sessions() const;
|
||||
|
||||
FdoSecretsPlugin* plugin() const
|
||||
{
|
||||
return m_plugin;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void doCloseDatabase(DatabaseWidget* dbWidget);
|
||||
Collection* doNewDatabase();
|
||||
void doSwitchToChangeDatabaseSettings(DatabaseWidget* dbWidget);
|
||||
void doUnlockDatabaseInDialog(DatabaseWidget* dbWidget);
|
||||
|
||||
private slots:
|
||||
void dbusServiceUnregistered(const QString& service);
|
||||
void ensureDefaultAlias();
|
||||
|
||||
void onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal);
|
||||
void monitorDatabaseExposedGroup(DatabaseWidget* dbWidget);
|
||||
|
||||
void onCollectionAliasAboutToAdd(const QString& alias);
|
||||
void onCollectionAliasAdded(const QString& alias);
|
||||
|
||||
void onCollectionAliasRemoved(const QString& alias);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Find collection by alias name
|
||||
* @param alias
|
||||
* @return the collection under alias
|
||||
*/
|
||||
Collection* findCollection(const QString& alias) const;
|
||||
|
||||
/**
|
||||
* Find collection by dbWidget
|
||||
* @param db
|
||||
* @return the collection corresponding to the db
|
||||
*/
|
||||
Collection* findCollection(const DatabaseWidget* db) const;
|
||||
|
||||
private:
|
||||
FdoSecretsPlugin* m_plugin;
|
||||
QPointer<DatabaseTabWidget> m_databases;
|
||||
|
||||
QHash<QString, Collection*> m_aliases;
|
||||
QList<Collection*> m_collections;
|
||||
QHash<const DatabaseWidget*, Collection*> m_dbToCollection;
|
||||
|
||||
QList<Session*> m_sessions;
|
||||
QHash<QString, Session*> m_peerToSession;
|
||||
|
||||
bool m_insdieEnsureDefaultAlias;
|
||||
|
||||
QScopedPointer<QDBusServiceWatcher> m_serviceWatcher;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_SERVICE_H
|
105
src/fdosecrets/objects/Session.cpp
Normal file
105
src/fdosecrets/objects/Session.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 "Session.h"
|
||||
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
|
||||
#include "core/Tools.h"
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
QHash<QString, QVariant> Session::negoniationState;
|
||||
|
||||
Session::Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent)
|
||||
: DBusObject(parent)
|
||||
, m_cipher(std::move(cipher))
|
||||
, m_peer(peer)
|
||||
, m_id(QUuid::createUuid())
|
||||
{
|
||||
registerWithPath(QStringLiteral(DBUS_PATH_TEMPLATE_SESSION).arg(p()->objectPath().path(), id()),
|
||||
new SessionAdaptor(this));
|
||||
}
|
||||
|
||||
void Session::CleanupNegotiation(const QString& peer)
|
||||
{
|
||||
negoniationState.remove(peer);
|
||||
}
|
||||
|
||||
DBusReturn<void> Session::close()
|
||||
{
|
||||
emit aboutToClose();
|
||||
deleteLater();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Session::peer() const
|
||||
{
|
||||
return m_peer;
|
||||
}
|
||||
|
||||
QString Session::id() const
|
||||
{
|
||||
return Tools::uuidToHex(m_id);
|
||||
}
|
||||
|
||||
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 == QLatin1Literal("plain")) {
|
||||
cipher.reset(new PlainCipher);
|
||||
} else if (algorithm == QLatin1Literal("dh-ietf1024-sha256-aes128-cbc-pkcs7")) {
|
||||
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
|
||||
{
|
||||
auto output = m_cipher->encrypt(input);
|
||||
output.session = objectPath();
|
||||
return output;
|
||||
}
|
||||
|
||||
SecretStruct Session::decode(const SecretStruct& input) const
|
||||
{
|
||||
return m_cipher->decrypt(input);
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
91
src/fdosecrets/objects/Session.h
Normal file
91
src/fdosecrets/objects/Session.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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_SESSION_H
|
||||
#define KEEPASSXC_FDOSECRETS_SESSION_H
|
||||
|
||||
#include "fdosecrets/objects/DBusObject.h"
|
||||
#include "fdosecrets/objects/Service.h"
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
#include "fdosecrets/objects/adaptors/SessionAdaptor.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QUuid>
|
||||
#include <QVariant>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class CipherPair;
|
||||
class Session : public DBusObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static std::unique_ptr<CipherPair> CreateCiphers(const QString& peer,
|
||||
const QString& algorithm,
|
||||
const QVariant& intpu,
|
||||
QVariant& output,
|
||||
bool& incomplete);
|
||||
static void CleanupNegotiation(const QString& peer);
|
||||
|
||||
explicit Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent);
|
||||
|
||||
DBusReturn<void> close();
|
||||
|
||||
/**
|
||||
* Encode the secret struct. Note only the value field is encoded.
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
SecretStruct encode(const SecretStruct& input) const;
|
||||
|
||||
/**
|
||||
* Decode the secret struct.
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
SecretStruct decode(const SecretStruct& input) const;
|
||||
|
||||
/**
|
||||
* The peer application that opened this session
|
||||
* @return
|
||||
*/
|
||||
QString peer() const;
|
||||
|
||||
QString id() const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The session is going to be closed
|
||||
* @param sess
|
||||
*/
|
||||
void aboutToClose();
|
||||
|
||||
private:
|
||||
std::unique_ptr<CipherPair> m_cipher;
|
||||
QString m_peer;
|
||||
QUuid m_id;
|
||||
|
||||
static QHash<QString, QVariant> negoniationState;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_SESSION_H
|
224
src/fdosecrets/objects/SessionCipher.cpp
Normal file
224
src/fdosecrets/objects/SessionCipher.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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 "SessionCipher.h"
|
||||
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
|
||||
#include <gcrypt.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr const auto IETF1024_SECOND_OAKLEY_GROUP_P_HEX = "FFFFFFFFFFFFFFFFC90FDAA22168C234"
|
||||
"C4C6628B80DC1CD129024E088A67CC74"
|
||||
"020BBEA63B139B22514A08798E3404DD"
|
||||
"EF9519B3CD3A431B302B0A6DF25F1437"
|
||||
"4FE1356D6D51C245E485B576625E7EC6"
|
||||
"F44C42E9A637ED6B0BFF5CB6F406B7ED"
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE6"
|
||||
"49286651ECE65381FFFFFFFFFFFFFFFF";
|
||||
constexpr const size_t KEY_SIZE_BYTES = 128;
|
||||
constexpr const int AES_KEY_LEN = 16; // 128 bits
|
||||
|
||||
const auto IETF1024_SECOND_OAKLEY_GROUP_P = MpiFromHex(IETF1024_SECOND_OAKLEY_GROUP_P_HEX, false);
|
||||
} // namespace
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
QVariant CipherPair::negotiationOutput() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes)
|
||||
: m_valid(false)
|
||||
{
|
||||
// read client public key
|
||||
auto clientPub = MpiFromBytes(clientPublicKeyBytes, false);
|
||||
|
||||
// generate server side private, 128 bytes
|
||||
GcryptMPI serverPrivate(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
||||
gcry_mpi_randomize(serverPrivate.get(), KEY_SIZE_BYTES * 8, GCRY_STRONG_RANDOM);
|
||||
|
||||
// generate server side public key
|
||||
GcryptMPI serverPublic(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
||||
// the generator of Second Oakley Group is 2
|
||||
gcry_mpi_powm(serverPublic.get(), GCRYMPI_CONST_TWO, serverPrivate.get(), IETF1024_SECOND_OAKLEY_GROUP_P.get());
|
||||
|
||||
initialize(std::move(clientPub), std::move(serverPublic), std::move(serverPrivate));
|
||||
}
|
||||
|
||||
bool
|
||||
DhIetf1024Sha256Aes128CbcPkcs7::initialize(GcryptMPI clientPublic, GcryptMPI serverPublic, GcryptMPI serverPrivate)
|
||||
{
|
||||
QByteArray commonSecretBytes;
|
||||
if (!diffieHullman(clientPublic, serverPrivate, commonSecretBytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_privateKey = MpiToBytes(serverPrivate);
|
||||
m_publicKey = MpiToBytes(serverPublic);
|
||||
|
||||
m_aesKey = hkdf(commonSecretBytes);
|
||||
|
||||
m_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DhIetf1024Sha256Aes128CbcPkcs7::diffieHullman(const GcryptMPI& clientPub,
|
||||
const GcryptMPI& serverPrivate,
|
||||
QByteArray& commonSecretBytes)
|
||||
{
|
||||
if (!clientPub || !serverPrivate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// calc common secret
|
||||
GcryptMPI commonSecret(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
||||
gcry_mpi_powm(commonSecret.get(), clientPub.get(), serverPrivate.get(), IETF1024_SECOND_OAKLEY_GROUP_P.get());
|
||||
commonSecretBytes = MpiToBytes(commonSecret);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray DhIetf1024Sha256Aes128CbcPkcs7::hkdf(const QByteArray& IKM)
|
||||
{
|
||||
// HKDF-Extract(salt, IKM) -> PRK
|
||||
// PRK = HMAC-Hash(salt, IKM)
|
||||
|
||||
// we use NULL salt as per spec
|
||||
auto PRK = CryptoHash::hmac(IKM,
|
||||
QByteArrayLiteral("\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0"),
|
||||
CryptoHash::Sha256);
|
||||
|
||||
// HKDF-Expand(PRK, info, L) -> OKM
|
||||
// N = ceil(L/HashLen)
|
||||
// T = T(1) | T(2) | T(3) | ... | T(N)
|
||||
// OKM = first L octets of T
|
||||
// where:
|
||||
// T(0) = empty string (zero length)
|
||||
// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
|
||||
// T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
|
||||
// T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
|
||||
// ...
|
||||
//
|
||||
// (where the constant concatenated to the end of each T(n) is a
|
||||
// single octet.)
|
||||
|
||||
// we use empty info as per spec
|
||||
// HashLen = 32 (sha256)
|
||||
// L = 16 (16 * 8 = 128 bits)
|
||||
// N = ceil(16/32) = 1
|
||||
|
||||
auto T1 = CryptoHash::hmac(QByteArrayLiteral("\x01"), PRK, CryptoHash::Sha256);
|
||||
|
||||
// resulting AES key is first 128 bits
|
||||
Q_ASSERT(T1.size() >= AES_KEY_LEN);
|
||||
auto OKM = T1.left(AES_KEY_LEN);
|
||||
return OKM;
|
||||
}
|
||||
|
||||
SecretStruct DhIetf1024Sha256Aes128CbcPkcs7::encrypt(const SecretStruct& input)
|
||||
{
|
||||
SecretStruct output = input;
|
||||
output.value.clear();
|
||||
output.parameters.clear();
|
||||
|
||||
SymmetricCipher encrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
|
||||
auto IV = randomGen()->randomArray(SymmetricCipher::algorithmIvSize(SymmetricCipher::Aes128));
|
||||
if (!encrypter.init(m_aesKey, IV)) {
|
||||
qWarning() << "Error encrypt: " << encrypter.errorString();
|
||||
return output;
|
||||
}
|
||||
|
||||
output.parameters = IV;
|
||||
|
||||
bool ok;
|
||||
output.value = input.value;
|
||||
output.value = encrypter.process(padPkcs7(output.value, encrypter.blockSize()), &ok);
|
||||
if (!ok) {
|
||||
qWarning() << "Error encrypt: " << encrypter.errorString();
|
||||
return output;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
QByteArray& DhIetf1024Sha256Aes128CbcPkcs7::padPkcs7(QByteArray& input, int blockSize)
|
||||
{
|
||||
// blockSize must be a power of 2.
|
||||
Q_ASSERT_X(blockSize > 0 && !(blockSize & (blockSize - 1)), "padPkcs7", "blockSize must be a power of 2");
|
||||
|
||||
int padLen = blockSize - (input.size() & (blockSize - 1));
|
||||
|
||||
input.append(QByteArray(padLen, static_cast<char>(padLen)));
|
||||
return input;
|
||||
}
|
||||
|
||||
SecretStruct DhIetf1024Sha256Aes128CbcPkcs7::decrypt(const SecretStruct& input)
|
||||
{
|
||||
auto IV = input.parameters;
|
||||
SymmetricCipher decrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!decrypter.init(m_aesKey, IV)) {
|
||||
qWarning() << "Error decoding: " << decrypter.errorString();
|
||||
return input;
|
||||
}
|
||||
bool ok;
|
||||
SecretStruct output = input;
|
||||
output.parameters.clear();
|
||||
output.value = decrypter.process(input.value, &ok);
|
||||
|
||||
if (!ok) {
|
||||
qWarning() << "Error decoding: " << decrypter.errorString();
|
||||
return input;
|
||||
}
|
||||
|
||||
unpadPkcs7(output.value);
|
||||
return output;
|
||||
}
|
||||
|
||||
QByteArray& DhIetf1024Sha256Aes128CbcPkcs7::unpadPkcs7(QByteArray& input)
|
||||
{
|
||||
if (input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
int padLen = input[input.size() - 1];
|
||||
input.chop(padLen);
|
||||
return input;
|
||||
}
|
||||
|
||||
bool DhIetf1024Sha256Aes128CbcPkcs7::isValid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
QVariant DhIetf1024Sha256Aes128CbcPkcs7::negotiationOutput() const
|
||||
{
|
||||
return m_publicKey;
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
128
src/fdosecrets/objects/SessionCipher.h
Normal file
128
src/fdosecrets/objects/SessionCipher.h
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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_SESSIONCIPHER_H
|
||||
#define KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H
|
||||
|
||||
#include "fdosecrets/GcryptMPI.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
class TestFdoSecrets;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class CipherPair
|
||||
{
|
||||
Q_DISABLE_COPY(CipherPair)
|
||||
public:
|
||||
CipherPair() = default;
|
||||
virtual ~CipherPair() = default;
|
||||
virtual SecretStruct encrypt(const SecretStruct& input) = 0;
|
||||
virtual SecretStruct decrypt(const SecretStruct& input) = 0;
|
||||
virtual bool isValid() const = 0;
|
||||
virtual QVariant negotiationOutput() const;
|
||||
};
|
||||
|
||||
class PlainCipher : public CipherPair
|
||||
{
|
||||
Q_DISABLE_COPY(PlainCipher)
|
||||
public:
|
||||
PlainCipher() = default;
|
||||
SecretStruct encrypt(const SecretStruct& input) override
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
SecretStruct decrypt(const SecretStruct& input) override
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
bool isValid() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class DhIetf1024Sha256Aes128CbcPkcs7 : public CipherPair
|
||||
{
|
||||
bool m_valid;
|
||||
QByteArray m_privateKey;
|
||||
QByteArray m_publicKey;
|
||||
QByteArray m_aesKey;
|
||||
|
||||
/**
|
||||
* Diffie Hullman Key Exchange
|
||||
* Given client public key, generate server private/public key pair and common secret.
|
||||
* This also sets m_publicKey to server's public key
|
||||
* @param clientPublicKey client public key
|
||||
* @param serverPrivate server private key
|
||||
* @param commonSecretBytes output common secret
|
||||
* @return true on success.
|
||||
*/
|
||||
bool
|
||||
diffieHullman(const GcryptMPI& clientPublicKey, const GcryptMPI& serverPrivate, QByteArray& commonSecretBytes);
|
||||
|
||||
/**
|
||||
* Perform HKDF defined in RFC5869, using sha256 as hash function
|
||||
* @param IKM input keying material
|
||||
* @return derived 128-bit key suitable for AES
|
||||
*/
|
||||
QByteArray hkdf(const QByteArray& IKM);
|
||||
|
||||
/**
|
||||
* Add PKCS#7 style padding to input inplace
|
||||
* @param input
|
||||
* @param blockSize the block size to use, must be 2's power
|
||||
* @return reference to input for chaining
|
||||
*/
|
||||
QByteArray& padPkcs7(QByteArray& input, int blockSize);
|
||||
|
||||
/**
|
||||
* Remove PKCS#7 style padding from input inplace
|
||||
* @param input
|
||||
* @return reference to input for chaining
|
||||
*/
|
||||
QByteArray& unpadPkcs7(QByteArray& input);
|
||||
|
||||
bool initialize(GcryptMPI clientPublic, GcryptMPI serverPublic, GcryptMPI serverPrivate);
|
||||
|
||||
DhIetf1024Sha256Aes128CbcPkcs7()
|
||||
: m_valid(false)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
explicit DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes);
|
||||
|
||||
SecretStruct encrypt(const SecretStruct& input) override;
|
||||
|
||||
SecretStruct decrypt(const SecretStruct& input) override;
|
||||
|
||||
bool isValid() const override;
|
||||
|
||||
QVariant negotiationOutput() const override;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(DhIetf1024Sha256Aes128CbcPkcs7);
|
||||
friend class ::TestFdoSecrets;
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H
|
89
src/fdosecrets/objects/adaptors/CollectionAdaptor.cpp
Normal file
89
src/fdosecrets/objects/adaptors/CollectionAdaptor.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
connect(
|
||||
p(), &Collection::itemCreated, this, [this](const Item* item) { emit ItemCreated(objectPathSafe(item)); });
|
||||
connect(
|
||||
p(), &Collection::itemDeleted, this, [this](const Item* item) { emit ItemDeleted(objectPathSafe(item)); });
|
||||
connect(
|
||||
p(), &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
|
71
src/fdosecrets/objects/adaptors/CollectionAdaptor.h
Normal file
71
src/fdosecrets/objects/adaptors/CollectionAdaptor.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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
|
51
src/fdosecrets/objects/adaptors/DBusAdaptor.h
Normal file
51
src/fdosecrets/objects/adaptors/DBusAdaptor.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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(QObject* parent = nullptr)
|
||||
: QDBusAbstractAdaptor(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~DBusAdaptor() override = default;
|
||||
|
||||
protected:
|
||||
Parent* p() const
|
||||
{
|
||||
return qobject_cast<Parent*>(parent());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
#endif // KEEPASSXC_FDOSECRETS_DBUSADAPTOR_H
|
83
src/fdosecrets/objects/adaptors/ItemAdaptor.cpp
Normal file
83
src/fdosecrets/objects/adaptors/ItemAdaptor.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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
|
62
src/fdosecrets/objects/adaptors/ItemAdaptor.h
Normal file
62
src/fdosecrets/objects/adaptors/ItemAdaptor.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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
|
47
src/fdosecrets/objects/adaptors/PromptAdaptor.cpp
Normal file
47
src/fdosecrets/objects/adaptors/PromptAdaptor.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
connect(p(), &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
|
46
src/fdosecrets/objects/adaptors/PromptAdaptor.h
Normal file
46
src/fdosecrets/objects/adaptors/PromptAdaptor.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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
|
137
src/fdosecrets/objects/adaptors/ServiceAdaptor.cpp
Normal file
137
src/fdosecrets/objects/adaptors/ServiceAdaptor.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
connect(p(), &Service::collectionCreated, this, [this](Collection* coll) {
|
||||
emit CollectionCreated(objectPathSafe(coll));
|
||||
});
|
||||
connect(p(), &Service::collectionDeleted, this, [this](Collection* coll) {
|
||||
emit CollectionDeleted(objectPathSafe(coll));
|
||||
});
|
||||
connect(p(), &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
|
71
src/fdosecrets/objects/adaptors/ServiceAdaptor.h
Normal file
71
src/fdosecrets/objects/adaptors/ServiceAdaptor.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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
|
35
src/fdosecrets/objects/adaptors/SessionAdaptor.cpp
Normal file
35
src/fdosecrets/objects/adaptors/SessionAdaptor.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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
|
42
src/fdosecrets/objects/adaptors/SessionAdaptor.h
Normal file
42
src/fdosecrets/objects/adaptors/SessionAdaptor.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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
|
173
src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.cpp
Normal file
173
src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 "DatabaseSettingsWidgetFdoSecrets.h"
|
||||
#include "ui_DatabaseSettingsWidgetFdoSecrets.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "gui/group/GroupModel.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class ExposedGroup
|
||||
{
|
||||
None,
|
||||
Expose
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class DatabaseSettingsWidgetFdoSecrets::GroupModelNoRecycle : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Database* m_db;
|
||||
|
||||
public:
|
||||
explicit GroupModelNoRecycle(Database* db)
|
||||
: m_db(db)
|
||||
{
|
||||
Q_ASSERT(db);
|
||||
setSourceModel(new GroupModel(m_db, this));
|
||||
}
|
||||
|
||||
Group* groupFromIndex(const QModelIndex& index) const
|
||||
{
|
||||
return groupFromSourceIndex(mapToSource(index));
|
||||
}
|
||||
|
||||
Group* groupFromSourceIndex(const QModelIndex& index) const
|
||||
{
|
||||
auto groupModel = qobject_cast<GroupModel*>(sourceModel());
|
||||
Q_ASSERT(groupModel);
|
||||
return groupModel->groupFromIndex(index);
|
||||
}
|
||||
|
||||
QModelIndex indexFromGroup(Group* group) const
|
||||
{
|
||||
auto groupModel = qobject_cast<GroupModel*>(sourceModel());
|
||||
Q_ASSERT(groupModel);
|
||||
return mapFromSource(groupModel->index(group));
|
||||
}
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
|
||||
{
|
||||
auto source_idx = sourceModel()->index(source_row, 0, source_parent);
|
||||
if (!source_idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto recycleBin = m_db->metadata()->recycleBin();
|
||||
if (!recycleBin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// can not call mapFromSource, which internally calls filterAcceptsRow
|
||||
auto group = groupFromSourceIndex(source_idx);
|
||||
|
||||
return group->uuid() != recycleBin->uuid();
|
||||
}
|
||||
};
|
||||
|
||||
DatabaseSettingsWidgetFdoSecrets::DatabaseSettingsWidgetFdoSecrets(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::DatabaseSettingsWidgetFdoSecrets)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->buttonGroup->setId(m_ui->radioDonotExpose, static_cast<int>(ExposedGroup::None));
|
||||
m_ui->buttonGroup->setId(m_ui->radioExpose, static_cast<int>(ExposedGroup::Expose));
|
||||
|
||||
// make sure there is at least a selection
|
||||
connect(m_ui->radioExpose, &QRadioButton::toggled, this, [this](bool checked) {
|
||||
if (checked && !m_ui->selectGroup->selectionModel()->hasSelection()) {
|
||||
auto model = m_ui->selectGroup->model();
|
||||
if (model) {
|
||||
auto idx = model->index(0, 0);
|
||||
m_ui->selectGroup->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DatabaseSettingsWidgetFdoSecrets::~DatabaseSettingsWidgetFdoSecrets() = default;
|
||||
|
||||
void DatabaseSettingsWidgetFdoSecrets::loadSettings(QSharedPointer<Database> db)
|
||||
{
|
||||
m_db = std::move(db);
|
||||
|
||||
m_model.reset(new GroupModelNoRecycle(m_db.data()));
|
||||
m_ui->selectGroup->setModel(m_model.data());
|
||||
|
||||
auto group = m_db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(m_db));
|
||||
if (!group) {
|
||||
m_ui->radioDonotExpose->setChecked(true);
|
||||
} else {
|
||||
auto idx = m_model->indexFromGroup(group);
|
||||
m_ui->selectGroup->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent);
|
||||
// expand all its parents
|
||||
idx = idx.parent();
|
||||
while (idx.isValid()) {
|
||||
m_ui->selectGroup->expand(idx);
|
||||
idx = idx.parent();
|
||||
}
|
||||
m_ui->radioExpose->setChecked(true);
|
||||
}
|
||||
|
||||
settingsWarning();
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidgetFdoSecrets::saveSettings()
|
||||
{
|
||||
Q_ASSERT(m_db);
|
||||
Q_ASSERT(m_model);
|
||||
|
||||
QUuid exposedGroup;
|
||||
switch (static_cast<ExposedGroup>(m_ui->buttonGroup->checkedId())) {
|
||||
case ExposedGroup::None:
|
||||
break;
|
||||
case ExposedGroup::Expose: {
|
||||
auto idx = m_ui->selectGroup->selectionModel()->selectedIndexes().takeFirst();
|
||||
Q_ASSERT(idx.isValid());
|
||||
exposedGroup = m_model->groupFromIndex(idx)->uuid();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FdoSecrets::settings()->setExposedGroup(m_db, exposedGroup);
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidgetFdoSecrets::settingsWarning()
|
||||
{
|
||||
if (FdoSecrets::settings()->isEnabled()) {
|
||||
m_ui->groupBox->setEnabled(true);
|
||||
m_ui->warningWidget->hideMessage();
|
||||
} else {
|
||||
m_ui->groupBox->setEnabled(false);
|
||||
m_ui->warningWidget->showMessage(tr("Enable fd.o Secret Service to access these settings."),
|
||||
MessageWidget::Warning);
|
||||
m_ui->warningWidget->setCloseButtonVisible(false);
|
||||
m_ui->warningWidget->setAutoHideTimeout(-1);
|
||||
}
|
||||
}
|
||||
|
||||
#include "DatabaseSettingsWidgetFdoSecrets.moc"
|
53
src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.h
Normal file
53
src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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_DATABASESETTINGSWIDGETFDOSECRETS_H
|
||||
#define KEEPASSXC_DATABASESETTINGSWIDGETFDOSECRETS_H
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class DatabaseSettingsWidgetFdoSecrets;
|
||||
}
|
||||
|
||||
class Database;
|
||||
class DatabaseSettingsWidgetFdoSecrets : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DatabaseSettingsWidgetFdoSecrets(QWidget* parent = nullptr);
|
||||
~DatabaseSettingsWidgetFdoSecrets() override;
|
||||
|
||||
void loadSettings(QSharedPointer<Database> db);
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
void settingsWarning();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::DatabaseSettingsWidgetFdoSecrets> m_ui;
|
||||
|
||||
QSharedPointer<Database> m_db;
|
||||
|
||||
class GroupModelNoRecycle;
|
||||
QScopedPointer<GroupModelNoRecycle> m_model;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSWIDGETFDOSECRETS_H
|
110
src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.ui
Normal file
110
src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.ui
Normal file
@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DatabaseSettingsWidgetFdoSecrets</class>
|
||||
<widget class="QWidget" name="DatabaseSettingsWidgetFdoSecrets">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="warningWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Exposed Entries</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioDonotExpose">
|
||||
<property name="text">
|
||||
<string>Don't e&xpose this database</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioExpose">
|
||||
<property name="text">
|
||||
<string>Expose entries &under this group:</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="selectGroup">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>radioExpose</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>selectGroup</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>199</x>
|
||||
<y>92</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>189</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
320
src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp
Normal file
320
src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* 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 "SettingsWidgetFdoSecrets.h"
|
||||
#include "ui_SettingsWidgetFdoSecrets.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#include "fdosecrets/FdoSecretsSettings.h"
|
||||
#include "fdosecrets/objects/Collection.h"
|
||||
#include "fdosecrets/objects/Prompt.h"
|
||||
#include "fdosecrets/objects/Session.h"
|
||||
|
||||
#include "core/DatabaseIcons.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QPushButton>
|
||||
#include <QTableWidget>
|
||||
#include <QToolBar>
|
||||
#include <QVariant>
|
||||
|
||||
using FdoSecrets::Collection;
|
||||
using FdoSecrets::Service;
|
||||
using FdoSecrets::Session;
|
||||
|
||||
SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::SettingsWidgetFdoSecrets())
|
||||
, m_plugin(plugin)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
auto sessHeader = m_ui->tableSessions->horizontalHeader();
|
||||
sessHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
sessHeader->setSectionsClickable(false);
|
||||
sessHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
|
||||
sessHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
|
||||
|
||||
auto dbHeader = m_ui->tableDatabases->horizontalHeader();
|
||||
dbHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
dbHeader->setSectionsClickable(false);
|
||||
dbHeader->setSectionResizeMode(0, QHeaderView::Stretch); // file name
|
||||
dbHeader->setSectionResizeMode(1, QHeaderView::Stretch); // group
|
||||
dbHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); // manage button
|
||||
|
||||
m_ui->tabWidget->setEnabled(m_ui->enableFdoSecretService->isChecked());
|
||||
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, m_ui->tabWidget, &QTabWidget::setEnabled);
|
||||
}
|
||||
|
||||
SettingsWidgetFdoSecrets::~SettingsWidgetFdoSecrets() = default;
|
||||
|
||||
void SettingsWidgetFdoSecrets::populateSessions(bool enabled)
|
||||
{
|
||||
m_ui->tableSessions->setRowCount(0);
|
||||
|
||||
auto service = m_plugin->serviceInstance();
|
||||
if (!service || !enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& sess : service->sessions()) {
|
||||
addSessionRow(sess);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::addSessionRow(Session* sess)
|
||||
{
|
||||
auto row = m_ui->tableSessions->rowCount();
|
||||
m_ui->tableSessions->insertRow(row);
|
||||
|
||||
// column 0: application name
|
||||
auto item = new QTableWidgetItem(sess->peer());
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(sess));
|
||||
m_ui->tableSessions->setItem(row, 0, item);
|
||||
|
||||
// column 1: disconnect button
|
||||
auto btn = new QPushButton(tr("Disconnect"));
|
||||
connect(btn, &QPushButton::clicked, sess, &Session::close);
|
||||
m_ui->tableSessions->setCellWidget(row, 1, btn);
|
||||
|
||||
// column 2: hidden uuid
|
||||
m_ui->tableSessions->setItem(row, 2, new QTableWidgetItem(sess->id()));
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::removeSessionRow(Session* sess)
|
||||
{
|
||||
int row = 0;
|
||||
while (row != m_ui->tableSessions->rowCount()) {
|
||||
auto item = m_ui->tableSessions->item(row, 0);
|
||||
const auto itemSess = item->data(Qt::UserRole).value<Session*>();
|
||||
if (itemSess == sess) {
|
||||
break;
|
||||
}
|
||||
++row;
|
||||
}
|
||||
if (row == m_ui->tableSessions->rowCount()) {
|
||||
qWarning() << "Unknown Fdo Secret Service session" << sess->id() << "while removing collection from table";
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->tableSessions->removeRow(row);
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::populateDatabases(bool enabled)
|
||||
{
|
||||
m_ui->tableDatabases->setRowCount(0);
|
||||
|
||||
auto service = m_plugin->serviceInstance();
|
||||
if (!service || !enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret = service->collections();
|
||||
if (ret.isError()) {
|
||||
return;
|
||||
}
|
||||
for (const auto& coll : ret.value()) {
|
||||
addDatabaseRow(coll);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::addDatabaseRow(Collection* coll)
|
||||
{
|
||||
auto row = m_ui->tableDatabases->rowCount();
|
||||
m_ui->tableDatabases->insertRow(row);
|
||||
|
||||
// column 0: File name
|
||||
QFileInfo fi(coll->backend()->database()->filePath());
|
||||
auto item = new QTableWidgetItem(fi.fileName());
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(coll));
|
||||
m_ui->tableDatabases->setItem(row, 0, item);
|
||||
|
||||
// column 2: manage button: hboxlayout: unlock/lock settings
|
||||
// create this first so we have a widget to bind connection to,
|
||||
// which can then be auto deleted when the row is deleted.
|
||||
auto widget = createManageButtons(coll);
|
||||
m_ui->tableDatabases->setCellWidget(row, 2, widget);
|
||||
|
||||
// column 1: Group name
|
||||
auto itemGroupName = new QTableWidgetItem();
|
||||
updateExposedGroupItem(itemGroupName, coll);
|
||||
|
||||
connect(coll, &Collection::collectionLockChanged, widget, [this, itemGroupName, coll](bool) {
|
||||
updateExposedGroupItem(itemGroupName, coll);
|
||||
});
|
||||
|
||||
m_ui->tableDatabases->setItem(row, 1, itemGroupName);
|
||||
}
|
||||
|
||||
QWidget* SettingsWidgetFdoSecrets::createManageButtons(Collection* coll)
|
||||
{
|
||||
auto toolbar = new QToolBar;
|
||||
toolbar->setFloatable(false);
|
||||
toolbar->setMovable(false);
|
||||
|
||||
// db settings
|
||||
auto dbSettingsAct = new QAction(tr("Database settings"), toolbar);
|
||||
dbSettingsAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("document-edit")));
|
||||
dbSettingsAct->setToolTip(tr("Edit database settings"));
|
||||
dbSettingsAct->setEnabled(!coll->locked().value());
|
||||
connect(dbSettingsAct, &QAction::triggered, this, [this, coll]() {
|
||||
auto db = coll->backend();
|
||||
m_plugin->serviceInstance()->doSwitchToChangeDatabaseSettings(db);
|
||||
});
|
||||
toolbar->addAction(dbSettingsAct);
|
||||
|
||||
// unlock/lock
|
||||
auto lockAct = new QAction(tr("Unlock database"), toolbar);
|
||||
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), true));
|
||||
lockAct->setToolTip(tr("Unlock database to show more information"));
|
||||
connect(coll, &Collection::collectionLockChanged, lockAct, [lockAct, dbSettingsAct](bool locked) {
|
||||
if (locked) {
|
||||
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), true));
|
||||
lockAct->setToolTip(tr("Unlock database to show more information"));
|
||||
} else {
|
||||
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-unlocked"), true));
|
||||
lockAct->setToolTip(tr("Lock database"));
|
||||
}
|
||||
dbSettingsAct->setEnabled(!locked);
|
||||
});
|
||||
connect(lockAct, &QAction::triggered, this, [coll]() {
|
||||
if (coll->locked().value()) {
|
||||
coll->doUnlock();
|
||||
} else {
|
||||
coll->doLock();
|
||||
}
|
||||
});
|
||||
toolbar->addAction(lockAct);
|
||||
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::updateExposedGroupItem(QTableWidgetItem* item, Collection* coll)
|
||||
{
|
||||
if (coll->locked().value()) {
|
||||
item->setText(tr("Unlock to show"));
|
||||
item->setIcon(filePath()->icon(QStringLiteral("apps"), QStringLiteral("object-locked"), true));
|
||||
QFont font;
|
||||
font.setItalic(true);
|
||||
item->setFont(font);
|
||||
return;
|
||||
}
|
||||
|
||||
auto db = coll->backend()->database();
|
||||
auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db));
|
||||
if (group) {
|
||||
item->setText(group->name());
|
||||
item->setIcon(group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
|
||||
: group->iconScaledPixmap());
|
||||
if (group->isExpired()) {
|
||||
QFont font;
|
||||
font.setStrikeOut(true);
|
||||
item->setFont(font);
|
||||
}
|
||||
} else {
|
||||
item->setText(tr("None"));
|
||||
item->setIcon(filePath()->icon(QStringLiteral("apps"), QStringLiteral("paint-none"), true));
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::removeDatabaseRow(Collection* coll)
|
||||
{
|
||||
int row = 0;
|
||||
while (row != m_ui->tableDatabases->rowCount()) {
|
||||
auto item = m_ui->tableDatabases->item(row, 0);
|
||||
const auto itemColl = item->data(Qt::UserRole).value<Collection*>();
|
||||
if (itemColl == coll) {
|
||||
break;
|
||||
}
|
||||
++row;
|
||||
}
|
||||
if (row == m_ui->tableDatabases->rowCount()) {
|
||||
qWarning() << "Unknown Fdo Secret Service collection" << coll->name() << "while removing collection from table";
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->tableDatabases->removeRow(row);
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::loadSettings()
|
||||
{
|
||||
m_ui->enableFdoSecretService->setChecked(FdoSecrets::settings()->isEnabled());
|
||||
m_ui->showNotification->setChecked(FdoSecrets::settings()->showNotification());
|
||||
m_ui->noConfirmDeleteItem->setChecked(FdoSecrets::settings()->noConfirmDeleteItem());
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::saveSettings()
|
||||
{
|
||||
FdoSecrets::settings()->setEnabled(m_ui->enableFdoSecretService->isChecked());
|
||||
FdoSecrets::settings()->setShowNotification(m_ui->showNotification->isChecked());
|
||||
FdoSecrets::settings()->setNoConfirmDeleteItem(m_ui->noConfirmDeleteItem->isChecked());
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::showEvent(QShowEvent* event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
|
||||
QMetaObject::invokeMethod(this, "updateTables", Qt::QueuedConnection, Q_ARG(bool, true));
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::hideEvent(QHideEvent* event)
|
||||
{
|
||||
QWidget::hideEvent(event);
|
||||
|
||||
QMetaObject::invokeMethod(this, "updateTables", Qt::QueuedConnection, Q_ARG(bool, false));
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::updateTables(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
// update the table
|
||||
populateDatabases(m_ui->enableFdoSecretService->isChecked());
|
||||
populateSessions(m_ui->enableFdoSecretService->isChecked());
|
||||
|
||||
// re-layout the widget to adjust the table cell size
|
||||
adjustSize();
|
||||
|
||||
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateSessions);
|
||||
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateDatabases);
|
||||
|
||||
auto service = m_plugin->serviceInstance();
|
||||
if (service) {
|
||||
connect(service, &Service::sessionOpened, this, &SettingsWidgetFdoSecrets::addSessionRow);
|
||||
connect(service, &Service::sessionClosed, this, &SettingsWidgetFdoSecrets::removeSessionRow);
|
||||
connect(service, &Service::collectionCreated, this, &SettingsWidgetFdoSecrets::addDatabaseRow);
|
||||
connect(service, &Service::collectionDeleted, this, &SettingsWidgetFdoSecrets::removeDatabaseRow);
|
||||
}
|
||||
} else {
|
||||
disconnect(
|
||||
m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateSessions);
|
||||
disconnect(
|
||||
m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateDatabases);
|
||||
|
||||
auto service = m_plugin->serviceInstance();
|
||||
if (service) {
|
||||
disconnect(service, &Service::sessionOpened, this, &SettingsWidgetFdoSecrets::addSessionRow);
|
||||
disconnect(service, &Service::sessionClosed, this, &SettingsWidgetFdoSecrets::removeSessionRow);
|
||||
disconnect(service, &Service::collectionCreated, this, &SettingsWidgetFdoSecrets::addDatabaseRow);
|
||||
disconnect(service, &Service::collectionDeleted, this, &SettingsWidgetFdoSecrets::removeDatabaseRow);
|
||||
}
|
||||
}
|
||||
}
|
76
src/fdosecrets/widgets/SettingsWidgetFdoSecrets.h
Normal file
76
src/fdosecrets/widgets/SettingsWidgetFdoSecrets.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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_SETTINGSWIDGETFDOSECRETS_H
|
||||
#define KEEPASSXC_SETTINGSWIDGETFDOSECRETS_H
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
class QTableWidgetItem;
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
||||
class Session;
|
||||
class Collection;
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
||||
class FdoSecretsPlugin;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class SettingsWidgetFdoSecrets;
|
||||
}
|
||||
class SettingsWidgetFdoSecrets : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent = nullptr);
|
||||
~SettingsWidgetFdoSecrets() override;
|
||||
|
||||
public slots:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
private slots:
|
||||
void populateSessions(bool enabled);
|
||||
void populateDatabases(bool enabled);
|
||||
void addSessionRow(FdoSecrets::Session* sess);
|
||||
void removeSessionRow(FdoSecrets::Session* sess);
|
||||
void addDatabaseRow(FdoSecrets::Collection* coll);
|
||||
void removeDatabaseRow(FdoSecrets::Collection* coll);
|
||||
|
||||
void updateTables(bool enabled);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
private:
|
||||
QWidget* createManageButtons(FdoSecrets::Collection* coll);
|
||||
|
||||
void updateExposedGroupItem(QTableWidgetItem* item, FdoSecrets::Collection* coll);
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::SettingsWidgetFdoSecrets> m_ui;
|
||||
FdoSecretsPlugin* m_plugin;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_SETTINGSWIDGETFDOSECRETS_H
|
162
src/fdosecrets/widgets/SettingsWidgetFdoSecrets.ui
Normal file
162
src/fdosecrets/widgets/SettingsWidgetFdoSecrets.ui
Normal file
@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SettingsWidgetFdoSecrets</class>
|
||||
<widget class="QWidget" name="SettingsWidgetFdoSecrets">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>525</width>
|
||||
<height>457</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="warningMsg" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableFdoSecretService">
|
||||
<property name="text">
|
||||
<string>Enable KeepassXC Freedesktop.org Secret Service integration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showNotification">
|
||||
<property name="text">
|
||||
<string>Show notification when credentials are requested</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="noConfirmDeleteItem">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If recycle bin is enabled for the database, entries will be moved to recycle bin directly. Otherwise, they will be deleted without confirmation.</p><p>You will still be prompted if any entries are referenced by others.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Don't confirm when entries are deleted by clients.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Exposed database groups:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableDatabases">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>File Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Group</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Manage</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Authorization</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>These applications are currently connected:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableSessions">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Application</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Manage</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -59,6 +59,11 @@
|
||||
#include "keeshare/KeeShare.h"
|
||||
#include "keeshare/SettingsPageKeeShare.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_FDOSECRETS
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#include "browser/BrowserOptionDialog.h"
|
||||
#include "browser/BrowserSettings.h"
|
||||
@ -181,6 +186,16 @@ MainWindow::MainWindow()
|
||||
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
|
||||
SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_FDOSECRETS
|
||||
auto fdoSS = new FdoSecretsPlugin(m_ui->tabWidget);
|
||||
connect(fdoSS, &FdoSecretsPlugin::error, this, &MainWindow::showErrorMessage);
|
||||
connect(fdoSS, &FdoSecretsPlugin::requestSwitchToDatabases, this, &MainWindow::switchToDatabases);
|
||||
connect(fdoSS, &FdoSecretsPlugin::requestShowNotification, this, &MainWindow::displayDesktopNotification);
|
||||
fdoSS->updateServiceState();
|
||||
m_ui->settingsWidget->addSettingsPage(fdoSS);
|
||||
#endif
|
||||
|
||||
setWindowIcon(filePath()->applicationIcon());
|
||||
m_ui->globalMessageWidget->setHidden(true);
|
||||
// clang-format off
|
||||
|
@ -28,6 +28,9 @@
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
#include "keeshare/DatabaseSettingsPageKeeShare.h"
|
||||
#endif
|
||||
#if defined(WITH_XC_FDOSECRETS)
|
||||
#include "fdosecrets/DatabaseSettingsPageFdoSecrets.h"
|
||||
#endif
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
@ -85,6 +88,10 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
|
||||
addSettingsPage(new DatabaseSettingsPageKeeShare());
|
||||
#endif
|
||||
|
||||
#if defined(WITH_XC_FDOSECRETS)
|
||||
addSettingsPage(new DatabaseSettingsPageFdoSecrets());
|
||||
#endif
|
||||
|
||||
m_ui->stackedWidget->setCurrentIndex(0);
|
||||
m_securityTabWidget->setCurrentIndex(0);
|
||||
|
||||
|
@ -220,6 +220,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
|
||||
add_unit_test(NAME testtools SOURCES TestTools.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
if(WITH_XC_FDOSECRETS)
|
||||
add_unit_test(NAME testfdosecrets SOURCES TestFdoSecrets.cpp
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
if(WITH_GUI_TESTS)
|
||||
# CLI clip tests need X environment on Linux
|
||||
|
92
tests/TestFdoSecrets.cpp
Normal file
92
tests/TestFdoSecrets.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 "TestFdoSecrets.h"
|
||||
|
||||
#include "TestGlobal.h"
|
||||
|
||||
#include "fdosecrets/GcryptMPI.h"
|
||||
#include "fdosecrets/objects/SessionCipher.h"
|
||||
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestFdoSecrets)
|
||||
|
||||
void TestFdoSecrets::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestFdoSecrets::cleanupTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
void TestFdoSecrets::testGcryptMPI()
|
||||
{
|
||||
auto bytes = QByteArray::fromHex(QByteArrayLiteral("DEADBEEF"));
|
||||
|
||||
auto mpi = MpiFromBytes(bytes);
|
||||
auto another = MpiFromHex("DEADBEEF");
|
||||
|
||||
// verify it can parse the bytes in USG mode
|
||||
QVERIFY(mpi.get());
|
||||
QVERIFY(another.get());
|
||||
|
||||
// verify the number is of the correct value
|
||||
QCOMPARE(gcry_mpi_cmp_ui(mpi.get(), 0xdeadbeef), 0);
|
||||
QCOMPARE(gcry_mpi_cmp_ui(another.get(), 0xdeadbeef), 0);
|
||||
|
||||
// verify it can convert back
|
||||
QCOMPARE(MpiToBytes(mpi), bytes);
|
||||
QCOMPARE(MpiToBytes(another), bytes);
|
||||
}
|
||||
|
||||
void TestFdoSecrets::testDhIetf1024Sha256Aes128CbcPkcs7()
|
||||
{
|
||||
auto clientPublic = MpiFromHex("40a0c8d27012c651bf270ebd96890a538"
|
||||
"396fae3852aef69c0c19bae420d667577"
|
||||
"ed471cd8ba5a49ef0ec91b568b95f87f0"
|
||||
"9ec31d271f1699ed140c5b38644c42f60"
|
||||
"ef84b5a6c406e17c07cd3208e5a605626"
|
||||
"a5266153b447529946be2394dd43e5638"
|
||||
"5ffbc4322902c2942391d1a36e8d125dc"
|
||||
"809e3e406a2f5c2dcf39d3da2");
|
||||
auto serverPublic = MpiFromHex("e407997e8b918419cf851cf3345358fdf"
|
||||
"ffb9564a220ac9c3934efd277cea20d17"
|
||||
"467ecdc56e817f75ac39501f38a4a04ff"
|
||||
"64d627e16c09981c7ad876da255b61c8e"
|
||||
"6a8408236c2a4523cfe6961c26dbdfc77"
|
||||
"c1a27a5b425ca71a019e829fae32c0b42"
|
||||
"0e1b3096b48bc2ce9ccab1d1ff13a5eb4"
|
||||
"b263cee30bdb1a57af9bfa93f");
|
||||
auto serverPrivate = MpiFromHex("013f4f3381ef0ca11c4c7363079577b56"
|
||||
"99b238644e0aba47e24bdba6173590216"
|
||||
"4f1e12dd0944800a373e090e63192f53b"
|
||||
"93583e9a9e50bb9d792aafaa3a0f5ae77"
|
||||
"de0c3423f5820848d88ee3bdd01c889f2"
|
||||
"7af58a02f5b6693d422b9d189b300d7b1"
|
||||
"be5076b5795cf8808c31e2e2898368d18"
|
||||
"ab5c26b0ea3480c9aba8154cf");
|
||||
|
||||
std::unique_ptr<FdoSecrets::DhIetf1024Sha256Aes128CbcPkcs7> cipher{new FdoSecrets::DhIetf1024Sha256Aes128CbcPkcs7};
|
||||
|
||||
cipher->initialize(std::move(clientPublic), std::move(serverPublic), std::move(serverPrivate));
|
||||
|
||||
QVERIFY(cipher->isValid());
|
||||
|
||||
QCOMPARE(cipher->m_aesKey.toHex(), QByteArrayLiteral("6b8f5ee55138eac37118508be21e7834"));
|
||||
}
|
35
tests/TestFdoSecrets.h
Normal file
35
tests/TestFdoSecrets.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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_TESTFDOSECRETS_H
|
||||
#define KEEPASSXC_TESTFDOSECRETS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class TestFdoSecrets : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void testGcryptMPI();
|
||||
void testDhIetf1024Sha256Aes128CbcPkcs7();
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_TESTFDOSECRETS_H
|
Loading…
Reference in New Issue
Block a user