From 7546ba740635fb01d11a497d8478d5e772412bce Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 13 Apr 2019 13:49:35 -0300 Subject: [PATCH 01/24] Remove hardcoded background color from search help widget --- src/gui/SearchHelpWidget.ui | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gui/SearchHelpWidget.ui b/src/gui/SearchHelpWidget.ui index daa3a851e..e4d77b1a0 100644 --- a/src/gui/SearchHelpWidget.ui +++ b/src/gui/SearchHelpWidget.ui @@ -16,9 +16,6 @@ false - - #SearchHelpWidget { background-color: #ffffff } - QFrame::Box From 663467e214edf66ef05adebdb0ddf60e71deec72 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 15 Apr 2019 14:34:16 -0400 Subject: [PATCH 02/24] Fix macOS Toolbar Button color * Correct color setting only if dark mode is enabled --- src/gui/MainWindow.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 077ee796e..bedd6c97f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -41,6 +41,10 @@ #include "keys/FileKey.h" #include "keys/PasswordKey.h" +#ifdef Q_OS_MACOS +#include "macutils/MacUtils.h" +#endif + #ifdef WITH_XC_UPDATECHECK #include "gui/MessageBox.h" #include "gui/UpdateCheckDialog.h" @@ -370,6 +374,9 @@ MainWindow::MainWindow() #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); + if (macUtils()->isDarkMode()) { + setStyleSheet("QToolButton {color:white;}"); + } #endif #ifdef WITH_XC_UPDATECHECK From 7067a4d004a3e5d6129ee94897c7c16f74b0cd45 Mon Sep 17 00:00:00 2001 From: Christian Kieschnick Date: Mon, 15 Apr 2019 17:09:17 +0200 Subject: [PATCH 03/24] Fix canceling cancel request in edited group In case of a modified group, pressing cancel in the confirmation dialog of cancel led to discarding the changes instead of returning to the edit widget. --- src/gui/group/EditGroupWidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index fe83a943e..051f23d4b 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -227,6 +227,9 @@ void EditGroupWidget::cancel() tr("Entry has unsaved changes"), MessageBox::Cancel | MessageBox::Save | MessageBox::Discard, MessageBox::Cancel); + if (result == MessageBox::Cancel) { + return; + } if (result == MessageBox::Save) { apply(); setModified(false); From a0c84dbd0d30696d153f050812974a975f99925e Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 16 Apr 2019 21:09:42 -0400 Subject: [PATCH 04/24] Bump version numbers to 2.4.2 --- CMakeLists.txt | 2 +- snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16d574f01..8405deb92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,7 @@ endif() set(KEEPASSXC_VERSION_MAJOR "2") set(KEEPASSXC_VERSION_MINOR "4") -set(KEEPASSXC_VERSION_PATCH "1") +set(KEEPASSXC_VERSION_PATCH "2") set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}") set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds") diff --git a/snapcraft.yaml b/snapcraft.yaml index fdeef7766..82b1a6a11 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: keepassxc -version: 2.4.1 +version: 2.4.2 grade: stable summary: Community-driven port of the Windows application “KeePass Password Safe” description: | From 7bd079d48dd43528faf745c8e11aa99bcb71cbd6 Mon Sep 17 00:00:00 2001 From: Allen Wild Date: Wed, 27 Mar 2019 19:56:10 -0400 Subject: [PATCH 05/24] add Lock Databases option to tray icon menu This is useful when keepassxc is minimized/hidden to the tray, and all the plumbing is already in place from the lock icon button in the main window UI. --- src/gui/MainWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index bedd6c97f..96475a0fb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -943,6 +943,8 @@ void MainWindow::updateTrayIcon() QAction* actionToggle = new QAction(tr("Toggle window"), menu); menu->addAction(actionToggle); + menu->addAction(m_ui->actionLockDatabases); + #ifdef Q_OS_MACOS QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu); menu->addAction(actionQuit); From 12e020b7c24c571d53b7628ead97b31de71a91a2 Mon Sep 17 00:00:00 2001 From: Xaver Maierhofer Date: Sat, 13 Apr 2019 14:29:56 +0200 Subject: [PATCH 06/24] Add option to prefer DuckDuckGo --- src/gui/ApplicationSettingsWidgetSecurity.ui | 2 +- src/gui/EditWidgetIcons.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/ApplicationSettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui index 344c2b81c..527110e64 100644 --- a/src/gui/ApplicationSettingsWidgetSecurity.ui +++ b/src/gui/ApplicationSettingsWidgetSecurity.ui @@ -212,7 +212,7 @@ - Use DuckDuckGo as fallback for downloading website icons + Use DuckDuckGo to download website icons diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 242ae4542..36c88e730 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -223,16 +223,16 @@ void EditWidgetIcons::downloadFavicon() QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); - m_urlsToTry.append(fallbackUrl); - if (!hostIsIp) { QString secondLevelDomain = getSecondLevelDomain(m_url); if (fullyQualifiedDomain != secondLevelDomain) { fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico"); - m_urlsToTry.append(fallbackUrl); + m_urlsToTry.prepend(fallbackUrl); } } + + m_urlsToTry.prepend(fallbackUrl); } startFetchFavicon(m_urlsToTry.takeFirst()); From 42d34a1999b159d54677bdc727b6df9e8716a22b Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 16 Apr 2019 20:14:10 -0400 Subject: [PATCH 07/24] Made changes to streamline icon downloading process --- src/core/Metadata.cpp | 2 +- src/gui/ApplicationSettingsWidgetSecurity.ui | 2 +- src/gui/EditWidgetIcons.cpp | 41 ++++++++++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp index 6448c391a..ff1ee71e7 100644 --- a/src/core/Metadata.cpp +++ b/src/core/Metadata.cpp @@ -195,7 +195,7 @@ QPixmap Metadata::customIconScaledPixmap(const QUuid& uuid) const QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid]; if (!QPixmapCache::find(cacheKey, &pixmap)) { - QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); pixmap = QPixmap::fromImage(image); cacheKey = QPixmapCache::insert(pixmap); } diff --git a/src/gui/ApplicationSettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui index 527110e64..bf5cce2d3 100644 --- a/src/gui/ApplicationSettingsWidgetSecurity.ui +++ b/src/gui/ApplicationSettingsWidgetSecurity.ui @@ -212,7 +212,7 @@ - Use DuckDuckGo to download website icons + Use DuckDuckGo service to download website icons diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 36c88e730..dcc5160a3 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -197,8 +197,6 @@ void EditWidgetIcons::downloadFavicon() QString fullyQualifiedDomain = m_url.host(); - m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico")); - // Determine if host portion of URL is an IP address by resolving it and // searching for a match with the returned address(es). bool hostIsIp = false; @@ -209,32 +207,35 @@ void EditWidgetIcons::downloadFavicon() } } + // Determine the second-level domain, if available + QString secondLevelDomain; if (!hostIsIp) { - QString secondLevelDomain = getSecondLevelDomain(m_url); - - // Attempt to simply load the favicon.ico file - if (fullyQualifiedDomain != secondLevelDomain) { - m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico")); - } + secondLevelDomain = getSecondLevelDomain(m_url); } - // Try to use alternative fallback URL, if enabled + // Start with the "fallback" url (if enabled) to try to get the best favicon if (config()->get("security/IconDownloadFallback", false).toBool()) { QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); + m_urlsToTry.append(fallbackUrl); - if (!hostIsIp) { - QString secondLevelDomain = getSecondLevelDomain(m_url); - - if (fullyQualifiedDomain != secondLevelDomain) { - fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico"); - m_urlsToTry.prepend(fallbackUrl); - } + // Also try a direct pull of the second-level domain (if possible) + if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { + fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico"); + m_urlsToTry.append(fallbackUrl); } - - m_urlsToTry.prepend(fallbackUrl); } + // Add a direct pull of the website's own favicon.ico file + m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico")); + + // Also try a direct pull of the second-level domain (if possible) + if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { + m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico")); + } + + // Use the first URL to start the download process + // If a favicon is not found, the next URL will be tried startFetchFavicon(m_urlsToTry.takeFirst()); #endif } @@ -277,7 +278,7 @@ void EditWidgetIcons::fetchFinished() if (!image.isNull()) { if (!addCustomIcon(image)) { emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); - } else if (!this->isVisible()) { + } else if (!isVisible()) { // Show confirmation message if triggered from Entry tab download button emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive); } @@ -289,7 +290,7 @@ void EditWidgetIcons::fetchFinished() if (!fallbackEnabled) { emit messageEditEntry( tr("Unable to fetch favicon.") + "\n" - + tr("Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security"), + + tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security"), MessageWidget::Error); } else { emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); From 5b28610c6a1b3efb299b9dac50e6ca240261dbbd Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 16 Apr 2019 20:18:18 -0400 Subject: [PATCH 08/24] Remove apply button from application settings --- src/gui/ApplicationSettingsWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index 22a49dece..2461230c8 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -64,6 +64,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) , m_globalAutoTypeModifiers(Qt::NoModifier) { setHeadline(tr("Application Settings")); + showApplyButton(false); m_secUi->setupUi(m_secWidget); m_generalUi->setupUi(m_generalWidget); @@ -75,7 +76,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) } connect(this, SIGNAL(accepted()), SLOT(saveSettings())); - connect(this, SIGNAL(apply()), SLOT(saveSettings())); connect(this, SIGNAL(rejected()), SLOT(reject())); // clang-format off From 7d46ce3de1f96076addeaba646f338fc2e3e2397 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 19 Apr 2019 16:19:19 -0400 Subject: [PATCH 09/24] Correct CLI help messages on Windows * Prevents keepassxc-cli.exe -> keepassxc-cli show.exe * Fixes #3032 --- src/cli/Add.cpp | 2 +- src/cli/Clip.cpp | 2 +- src/cli/Create.cpp | 2 +- src/cli/Diceware.cpp | 4 ++-- src/cli/Edit.cpp | 2 +- src/cli/Estimate.cpp | 2 +- src/cli/Extract.cpp | 2 +- src/cli/Generate.cpp | 4 ++-- src/cli/List.cpp | 2 +- src/cli/Locate.cpp | 2 +- src/cli/Merge.cpp | 2 +- src/cli/Remove.cpp | 2 +- src/cli/Show.cpp | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 395b84919..975d549e5 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -84,7 +84,7 @@ int Add::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add"); + errorTextStream << parser.helpText().replace("[options]", "add [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 31b421de6..e1e74c682 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -63,7 +63,7 @@ int Clip::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2 && args.size() != 3) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli clip"); + errorTextStream << parser.helpText().replace("[options]", "clip [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Create.cpp b/src/cli/Create.cpp index b8c094f90..80dcb5691 100644 --- a/src/cli/Create.cpp +++ b/src/cli/Create.cpp @@ -70,7 +70,7 @@ int Create::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() < 1) { - out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create"); + out << parser.helpText().replace("[options]", "create [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index f11347344..c663cfc39 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -58,7 +58,7 @@ int Diceware::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (!args.isEmpty()) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + errorTextStream << parser.helpText().replace("[options]", "diceware [options]"); return EXIT_FAILURE; } @@ -78,7 +78,7 @@ int Diceware::execute(const QStringList& arguments) } if (!dicewareGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + outputTextStream << parser.helpText().replace("[options]", "diceware [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 76e996c98..59cedd7c9 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -88,7 +88,7 @@ int Edit::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); + errorTextStream << parser.helpText().replace("[options]", "edit [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Estimate.cpp b/src/cli/Estimate.cpp index 7064963f4..c278b50f3 100644 --- a/src/cli/Estimate.cpp +++ b/src/cli/Estimate.cpp @@ -171,7 +171,7 @@ int Estimate::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() > 1) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); + errorTextStream << parser.helpText().replace("[options]", "estimate [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 729687fe3..2e4e6f9cd 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -57,7 +57,7 @@ int Extract::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 1) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract"); + errorTextStream << parser.helpText().replace("[options]", "extract [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index 5f0ad98ac..e8ca90275 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -84,7 +84,7 @@ int Generate::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (!args.isEmpty()) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); + errorTextStream << parser.helpText().replace("[options]", "generate [options]"); return EXIT_FAILURE; } @@ -128,7 +128,7 @@ int Generate::execute(const QStringList& arguments) passwordGenerator.setExcludedChars(parser.value(exclude)); if (!passwordGenerator.isValid()) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); + errorTextStream << parser.helpText().replace("[options]", "generate [options]"); return EXIT_FAILURE; } diff --git a/src/cli/List.cpp b/src/cli/List.cpp index ebf7bfda1..52797470c 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -59,7 +59,7 @@ int List::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 1 && args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli ls"); + errorTextStream << parser.helpText().replace("[options]", "ls [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index 81bbdd55d..af5f24196 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -56,7 +56,7 @@ int Locate::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate"); + errorTextStream << parser.helpText().replace("[options]", "locate [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index a7357394f..2356f5d3a 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -69,7 +69,7 @@ int Merge::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge"); + errorTextStream << parser.helpText().replace("[options]", "merge [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index 07da23b7b..bb2374e9a 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -58,7 +58,7 @@ int Remove::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); + errorTextStream << parser.helpText().replace("[options]", "rm [options]"); return EXIT_FAILURE; } diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index d16fbfe3c..3abccd79c 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -69,7 +69,7 @@ int Show::execute(const QStringList& arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli show"); + errorTextStream << parser.helpText().replace("[options]", "show [options]"); return EXIT_FAILURE; } From cb442f8c6e913ae0defc72388a37f01ca9a37058 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 20 Apr 2019 11:51:15 -0400 Subject: [PATCH 10/24] Don't mark entry edit as modified when attribute selection changes (#3041) When selecting another attribute in the advanced tab, do not mark the entry as modified (nothing was changed). Also do not mark as modified when the notes checkbox is checked/unchecked (doesn't change entry). Fixes #3013. --- src/gui/entry/EditEntryWidget.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 063f8da2c..e22e6703e 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -285,7 +285,6 @@ void EditEntryWidget::setupEntryUpdate() connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString))); #endif connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified())); - connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setModified())); connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified())); connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified())); @@ -1111,8 +1110,9 @@ void EditEntryWidget::updateCurrentAttribute() void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) { - // Block signals to prevent extra calls + // Block signals to prevent modified being set m_advancedUi->protectAttributeButton->blockSignals(true); + m_advancedUi->attributesEdit->blockSignals(true); if (index.isValid()) { QString key = m_attributesModel->keyByIndex(index); @@ -1143,6 +1143,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) } m_advancedUi->protectAttributeButton->blockSignals(false); + m_advancedUi->attributesEdit->blockSignals(false); } void EditEntryWidget::protectCurrentAttribute(bool state) From acd6847cd4d6c53b1034e6c018c2e2b7f2399ff6 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 20 Apr 2019 11:54:25 -0400 Subject: [PATCH 11/24] Support Ctrl+Enter shortcut on all dialogs with QPushButtonBox (#3039) * Remove specific action from EditEntryWidget * Implement key handling at the lowest level * Fix #3036 --- src/gui/DialogyWidget.cpp | 3 ++- src/gui/entry/EditEntryWidget.cpp | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/gui/DialogyWidget.cpp b/src/gui/DialogyWidget.cpp index 858d2949b..597bcc59d 100644 --- a/src/gui/DialogyWidget.cpp +++ b/src/gui/DialogyWidget.cpp @@ -35,7 +35,8 @@ void DialogyWidget::keyPressEvent(QKeyEvent* e) } } else #endif - if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { + if (!e->modifiers() || e->modifiers() == Qt::ControlModifier + || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { switch (e->key()) { case Qt::Key_Enter: case Qt::Key_Return: diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index e22e6703e..8d0f6f556 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -152,11 +152,6 @@ void EditEntryWidget::setupMain() m_mainUi->expirePresets->setMenu(createPresetsMenu()); connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*))); - QAction* action = new QAction(this); - action->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(action, SIGNAL(triggered()), this, SLOT(commitEntry())); - this->addAction(action); - m_mainUi->passwordGenerator->hide(); m_mainUi->passwordGenerator->reset(); } From bbe7e8a45a463adbd210856bd18b50604869bf5e Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 20 Apr 2019 12:00:45 -0400 Subject: [PATCH 12/24] Use QLocale for translation search instead of custom method (#3035) Use built-in facilities of Qt to traverse QLocale::uiLanguages() to find a valid "most preferred" language, but still respect user's choice in the application settings. Fixes #3030. Fixes #1924. --- src/core/Translator.cpp | 27 ++++++++++++++------------- src/core/Translator.h | 5 +++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/core/Translator.cpp b/src/core/Translator.cpp index 595dadfa1..95de3ce91 100644 --- a/src/core/Translator.cpp +++ b/src/core/Translator.cpp @@ -34,13 +34,14 @@ */ void Translator::installTranslators() { + QLocale locale; QString language = config()->get("GUI/Language").toString(); - if (language == "system" || language.isEmpty()) { - language = QLocale::system().name(); - } - if (language == "en") { + if (!language.isEmpty() && language != "system") { // use actual English translation instead of the English locale source language - language = "en_US"; + if (language == "en") { + language = "en_US"; + } + locale = QLocale(language); } const QStringList paths = { @@ -51,11 +52,12 @@ void Translator::installTranslators() bool translationsLoaded = false; for (const QString& path : paths) { - translationsLoaded |= installTranslator(language, path) || installTranslator("en_US", path); + translationsLoaded |= installTranslator(locale, path) || installTranslator(QLocale("en_US"), path); if (!installQtTranslator(language, path)) { - installQtTranslator("en", path); + installQtTranslator(QLocale("en"), path); } } + if (!translationsLoaded) { // couldn't load configured language or fallback qWarning("Couldn't load translations."); @@ -114,10 +116,10 @@ QList> Translator::availableLanguages() * @param path local search path * @return true on success */ -bool Translator::installTranslator(const QString& language, const QString& path) +bool Translator::installTranslator(const QLocale& locale, const QString& path) { QScopedPointer translator(new QTranslator(qApp)); - if (translator->load(QString("keepassx_%1").arg(language), path)) { + if (translator->load(locale, "keepassx_", "", path)) { return QCoreApplication::installTranslator(translator.take()); } return false; @@ -131,13 +133,12 @@ bool Translator::installTranslator(const QString& language, const QString& path) * @param path local search path * @return true on success */ -bool Translator::installQtTranslator(const QString& language, const QString& path) +bool Translator::installQtTranslator(const QLocale& locale, const QString& path) { QScopedPointer qtTranslator(new QTranslator(qApp)); - if (qtTranslator->load(QString("qtbase_%1").arg(language), path)) { + if (qtTranslator->load(locale, "qtbase_", "", path)) { return QCoreApplication::installTranslator(qtTranslator.take()); - } else if (qtTranslator->load(QString("qtbase_%1").arg(language), - QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + } else if (qtTranslator->load(locale, "qtbase_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { return QCoreApplication::installTranslator(qtTranslator.take()); } return false; diff --git a/src/core/Translator.h b/src/core/Translator.h index cf62f48e4..cfc49d710 100644 --- a/src/core/Translator.h +++ b/src/core/Translator.h @@ -20,6 +20,7 @@ #include #include +#include class Translator { @@ -28,8 +29,8 @@ public: static QList> availableLanguages(); private: - static bool installTranslator(const QString& language, const QString& path); - static bool installQtTranslator(const QString& language, const QString& path); + static bool installTranslator(const QLocale& locale, const QString& path); + static bool installQtTranslator(const QLocale& locale, const QString& path); }; #endif // KEEPASSX_TRANSLATOR_H From 219a0f40ff120f1e4beb4b51ab48edb4106e3d2a Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 20 Apr 2019 12:10:07 -0400 Subject: [PATCH 13/24] Prevent infinite save loop when location is unavailable (#3026) This bug impacted unsafe saves. When auto save after every change was enabled, an unsafe save to a location that has become unavailable (eg, dismounted veracrypt drive), the database modified signal would continually activate a save action that failed. This caused an infinite loop. When auto-save on exit was enabled, the database tab and the application itself refused to close if saving failed for whatever reason. The fixes in this commit prevent both of these scenarios from occurring. --- src/gui/DatabaseWidget.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 8cfc40815..e4f175bf2 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1158,9 +1158,10 @@ void DatabaseWidget::onDatabaseModified() { if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) { save(); + } else { + // Only block once, then reset + m_blockAutoSave = false; } - - m_blockAutoSave = false; } QString DatabaseWidget::getCurrentSearch() @@ -1258,11 +1259,13 @@ bool DatabaseWidget::lock() } if (m_db->isModified()) { + bool saved = false; + // Attempt to save on exit, but don't block locking if it fails if (config()->get("AutoSaveOnExit").toBool()) { - if (!save()) { - return false; - } - } else { + saved = save(); + } + + if (!saved) { QString msg; if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) { msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped()); @@ -1521,11 +1524,14 @@ bool DatabaseWidget::save() return true; } + // Read-only and new databases ask for filename if (m_db->isReadOnly() || m_db->filePath().isEmpty()) { return saveAs(); } + // Prevent recursions and infinite save loops blockAutoReload(true); + m_blockAutoSave = true; ++m_saveAttempts; // TODO: Make this async, but lock out the database widget to prevent re-entrance @@ -1536,6 +1542,7 @@ bool DatabaseWidget::save() if (ok) { m_saveAttempts = 0; + m_blockAutoSave = false; return true; } From 53796a216ebb6269bc199adab4d8e47d2182988a Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 20 Apr 2019 12:12:00 -0400 Subject: [PATCH 14/24] Windows: use winqtdeploy instead of DeplyQt4 from CMake (#3025) * Ensure Qt dlls find plugins in bundled directory * Reduce complexity of deployment code * Standardize use of CMAKE_BUILD_TYPE_LOWER for more robust comparisons Fixes #3023. Fixes part of #1535. --- CMakeLists.txt | 17 ++++++++++++----- cmake/CodeCoverage.cmake | 2 +- src/CMakeLists.txt | 30 ++++++++++++------------------ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8405deb92..407a27542 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,10 @@ project(KeePassXC) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING - "Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel." + "Choose the type of build, options are: Debug Release RelWithDebInfo Profile" FORCE) endif() +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -199,7 +200,7 @@ add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute") add_gcc_compiler_flags("-fvisibility=hidden") add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden") -if(CMAKE_BUILD_TYPE STREQUAL "Debug") +if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") add_gcc_compiler_flags("-Werror") endif() @@ -230,7 +231,6 @@ if(WITH_ASAN) endif() -string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2") endif() @@ -276,7 +276,7 @@ if(MINGW) set(CMAKE_RC_COMPILER_INIT windres) enable_language(RC) set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") - if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + if(NOT (CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo")) # Enable DEP and ASLR set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") @@ -365,10 +365,17 @@ if(APPLE) set(CMAKE_MACOSX_RPATH TRUE) find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH) if(NOT MACDEPLOYQT_EXE) - message(FATAL_ERROR "macdeployqt is required to build in macOS") + message(FATAL_ERROR "macdeployqt is required to build on macOS") else() message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}") endif() +elseif(MINGW) + find_program(WINDEPLOYQT_EXE windeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH) + if(NOT WINDEPLOYQT_EXE) + message(FATAL_ERROR "windeployqt is required to build on Windows") + else() + message(STATUS "Using windeployqt: ${WINDEPLOYQT_EXE}") + endif() endif() # Debian sets the the build type to None for package builds. diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index d10791745..f5287b75b 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -112,7 +112,7 @@ mark_as_advanced( CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) -if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") +if(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d8eb681e3..9b009b698 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -410,25 +410,19 @@ if(MINGW) install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime) - include(DeployQt4) - install_qt4_executable(${PROGNAME}.exe) + # Deploy all 3rd party library dependencies first + install(CODE "include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${PROGNAME}.exe\" \"\" \"\")" + COMPONENT Runtime) - # install Qt5 plugins - set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins) - install(FILES - ${PLUGINS_DIR}/platforms/qwindows$<$:d>.dll - ${PLUGINS_DIR}/platforms/qdirect2d$<$:d>.dll - DESTINATION "platforms") - install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$:d>.dll DESTINATION "styles") - install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$:d>.dll DESTINATION "platforminputcontexts") - install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$:d>.dll DESTINATION "iconengines") - install(FILES - ${PLUGINS_DIR}/imageformats/qgif$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qicns$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qico$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qjpeg$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qwebp$<$:d>.dll - DESTINATION "imageformats") + # Use windeployqt.exe to setup Qt dependencies + set(WINDEPLOYQT_MODE "--release") + if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") + set(WINDEPLOYQT_MODE "--debug") + endif() + + install(CODE "execute_process(COMMAND ${WINDEPLOYQT_EXE} ${PROGNAME}.exe ${WINDEPLOYQT_MODE} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX} OUTPUT_QUIET)" + COMPONENT Runtime) # install CA cert chains install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs") From c7898fdeee07b17939d2e5af4bb507493b2d8a0b Mon Sep 17 00:00:00 2001 From: joshirio Date: Sat, 20 Apr 2019 20:05:54 +0200 Subject: [PATCH 15/24] Snap: fix session database locking --- snapcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapcraft.yaml b/snapcraft.yaml index 82b1a6a11..0aafd7a19 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -18,7 +18,7 @@ plugs: apps: keepassxc: command: desktop-launch keepassxc - plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy] + plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy, desktop] desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop environment: DISABLE_WAYLAND: 1 @@ -82,7 +82,7 @@ parts: stage: - -opt after: [desktop-qt5] - + desktop-qt5: source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source-subdir: qt From 13eb1c0bbdf07312f099099c7ca571c6a77eafa1 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 21 Feb 2019 22:28:45 +0100 Subject: [PATCH 16/24] Improve resilience against memory attacks To reduce residual fragments of secret data in memory after deallocation, this patch replaces the global delete operator with a version that zeros out previously allocated memory. It makes use of the new C++14 sized deallocation, but provides an unsized fallback with platform-specific size deductions. This change is only a minor mitigation and cannot protect against buffer reallocations by the operating system or non-C++ libraries. Thus, we still cannot guarantee all memory to be wiped after free. As a further improvement, this patch uses libgcrypt and libsodium to write long-lived master key component hashes into a secure memory area and wipe it afterwards. The patch also fixes compiler flags not being set properly on macOS. --- CMakeLists.txt | 18 ++++-- INSTALL.md | 2 +- src/CMakeLists.txt | 4 ++ src/browser/CMakeLists.txt | 3 +- src/cli/CMakeLists.txt | 1 + src/core/Alloc.cpp | 89 +++++++++++++++++++++++++++++ src/keys/FileKey.cpp | 46 ++++++++++++--- src/keys/FileKey.h | 5 +- src/keys/PasswordKey.cpp | 24 ++++++-- src/keys/PasswordKey.h | 5 +- src/keys/YkChallengeResponseKey.cpp | 27 ++++++++- src/keys/YkChallengeResponseKey.h | 6 +- src/proxy/CMakeLists.txt | 3 +- src/touchid/TouchID.mm | 2 + 14 files changed, 207 insertions(+), 28 deletions(-) create mode 100644 src/core/Alloc.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 407a27542..0a84ef18c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF) option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF) option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON) -set(WITH_XC_ALL OFF CACHE BOOLEAN "Build in all available plugins") +set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins") option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF) @@ -163,11 +163,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") set(IS_32BIT TRUE) endif() -if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") +if("${CMAKE_C_COMPILER}" MATCHES "clang$" + OR "${CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__" + OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_COMPILER_IS_CLANG 1) endif() -if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") +if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" + OR "${CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__" + OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_COMPILER_IS_CLANGXX 1) endif() @@ -264,6 +268,11 @@ endif() add_gcc_compiler_cflags("-std=c99") add_gcc_compiler_cxxflags("-std=c++11") +if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.99) OR + (CMAKE_COMPILER_IS_CLANGXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6.99)) + add_gcc_compiler_cxxflags("-fsized-deallocation") +endif() + if(APPLE) add_gcc_compiler_cxxflags("-stdlib=libc++") endif() @@ -387,6 +396,7 @@ find_package(Gcrypt 1.7.0 REQUIRED) find_package(Argon2 REQUIRED) find_package(ZLIB REQUIRED) find_package(QREncode REQUIRED) +find_package(sodium 1.0.12 REQUIRED) set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) @@ -394,7 +404,7 @@ if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0") message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format") endif() -include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) +include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR}) # Optional if(WITH_XC_KEESHARE) diff --git a/INSTALL.md b/INSTALL.md index d3927536f..3bc9185d9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -25,7 +25,7 @@ The following libraries are required: * zlib * libmicrohttpd * libxi, libxtst, qtx11extras (optional for auto-type on X11) -* libsodium (>= 1.0.12, optional for KeePassXC-Browser support) +* libsodium (>= 1.0.12) * libargon2 Prepare the Building Environment diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b009b698..16d0ef89c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,7 @@ if(NOT ZXCVBN_LIBRARIES) endif(NOT ZXCVBN_LIBRARIES) set(keepassx_SOURCES + core/Alloc.cpp core/AutoTypeAssociations.cpp core/AutoTypeMatch.cpp core/Compare.cpp @@ -254,6 +255,8 @@ endif() if(WITH_XC_TOUCHID) list(APPEND keepassx_SOURCES touchid/TouchID.mm) + # TODO: Remove -Wno-error once deprecation warnings have been resolved. + set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error") endif() add_library(autotype STATIC ${autotype_SOURCES}) @@ -270,6 +273,7 @@ target_link_libraries(keepassx_core Qt5::Concurrent Qt5::Network Qt5::Widgets + ${sodium_LIBRARY_RELEASE} ${YUBIKEY_LIBRARIES} ${ZXCVBN_LIBRARIES} ${ARGON2_LIBRARIES} diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt index 10189d931..7e813eb5b 100755 --- a/src/browser/CMakeLists.txt +++ b/src/browser/CMakeLists.txt @@ -16,7 +16,6 @@ if(WITH_XC_BROWSER) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) - find_package(sodium 1.0.12 REQUIRED) set(keepassxcbrowser_SOURCES BrowserAccessControlDialog.cpp @@ -33,5 +32,5 @@ if(WITH_XC_BROWSER) Variant.cpp) add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES}) - target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium) + target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE}) endif() diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index c3f97a2cd..2f4a7275e 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli keepassx_core Qt5::Core ${GCRYPT_LIBRARIES} + ${sodium_LIBRARY_RELEASE} ${ARGON2_LIBRARIES} ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES} diff --git a/src/core/Alloc.cpp b/src/core/Alloc.cpp new file mode 100644 index 000000000..a33b56196 --- /dev/null +++ b/src/core/Alloc.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#include +#include +#include +#ifdef Q_OS_MACOS +#include +#else +#include +#endif + +#if defined(NDEBUG) && !defined(__cpp_sized_deallocation) +#warning "KeePassXC is being compiled without sized deallocation support. Deletes may be slow." +#endif + +/** + * Custom sized delete operator which securely zeroes out allocated + * memory before freeing it (requires C++14 sized deallocation support). + */ +void operator delete(void* ptr, std::size_t size) noexcept +{ + if (!ptr) { + return; + } + + sodium_memzero(ptr, size); + std::free(ptr); +} + +void operator delete[](void* ptr, std::size_t size) noexcept +{ + ::operator delete(ptr, size); +} + +/** + * Custom delete operator which securely zeroes out + * allocated memory before freeing it. + */ +void operator delete(void* ptr) noexcept +{ + if (!ptr) { + return; + } + +#if defined(Q_OS_WIN) + ::operator delete(ptr, _msize(ptr)); +#elif defined(Q_OS_MACOS) + ::operator delete(ptr, malloc_size(ptr)); +#elif defined(Q_OS_UNIX) + ::operator delete(ptr, malloc_usable_size(ptr)); +#else + // whatever OS this is, give up and simply free stuff + std::free(ptr); +#endif +} + +void operator delete[](void* ptr) noexcept +{ + ::operator delete(ptr); +} + +/** + * Custom insecure delete operator that does not zero out memory before + * freeing a buffer. Can be used for better performance. + */ +void operator delete(void* ptr, bool) noexcept +{ + std::free(ptr); +} + +void operator delete[](void* ptr, bool) noexcept +{ + ::operator delete(ptr, false); +} diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp index 9d1e8f50f..da25ef4ae 100644 --- a/src/keys/FileKey.cpp +++ b/src/keys/FileKey.cpp @@ -18,19 +18,35 @@ #include "FileKey.h" -#include - #include "core/Tools.h" #include "crypto/CryptoHash.h" #include "crypto/Random.h" +#include + +#include +#include +#include +#include + QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273"); +constexpr int FileKey::SHA256_SIZE; + FileKey::FileKey() : Key(UUID) + , m_key(static_cast(gcry_malloc_secure(SHA256_SIZE))) { } +FileKey::~FileKey() +{ + if (m_key) { + gcry_free(m_key); + m_key = nullptr; + } +} + /** * Read key file from device while trying to detect its file format. * @@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg) */ QByteArray FileKey::rawKey() const { - return m_key; + if (!m_key) { + return {}; + } + return QByteArray::fromRawData(m_key, SHA256_SIZE); } /** @@ -223,12 +242,15 @@ bool FileKey::loadXml(QIODevice* device) } } + bool ok = false; if (!xmlReader.error() && correctMeta && !data.isEmpty()) { - m_key = data; - return true; + std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); + ok = true; } - return false; + sodium_memzero(data.data(), static_cast(data.capacity())); + + return ok; } /** @@ -293,7 +315,8 @@ bool FileKey::loadBinary(QIODevice* device) if (!Tools::readAllFromDevice(device, data) || data.size() != 32) { return false; } else { - m_key = data; + std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); + sodium_memzero(data.data(), static_cast(data.capacity())); return true; } } @@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device) } QByteArray key = QByteArray::fromHex(data); + sodium_memzero(data.data(), static_cast(data.capacity())); if (key.size() != 32) { return false; } - m_key = key; + std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size())); + sodium_memzero(key.data(), static_cast(key.capacity())); + return true; } @@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device) cryptoHash.addData(buffer); } while (!buffer.isEmpty()); - m_key = cryptoHash.result(); + auto result = cryptoHash.result(); + std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size())); + sodium_memzero(result.data(), static_cast(result.capacity())); return true; } diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h index d7486467b..290a04af0 100644 --- a/src/keys/FileKey.h +++ b/src/keys/FileKey.h @@ -40,6 +40,7 @@ public: }; FileKey(); + ~FileKey() override; bool load(QIODevice* device); bool load(const QString& fileName, QString* errorMsg = nullptr); QByteArray rawKey() const override; @@ -48,6 +49,8 @@ public: static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128); private: + static constexpr int SHA256_SIZE = 32; + bool loadXml(QIODevice* device); bool loadXmlMeta(QXmlStreamReader& xmlReader); QByteArray loadXmlKey(QXmlStreamReader& xmlReader); @@ -55,7 +58,7 @@ private: bool loadHex(QIODevice* device); bool loadHashed(QIODevice* device); - QByteArray m_key; + char* m_key = nullptr; Type m_type = None; }; diff --git a/src/keys/PasswordKey.cpp b/src/keys/PasswordKey.cpp index 35ecb9989..2d0416af8 100644 --- a/src/keys/PasswordKey.cpp +++ b/src/keys/PasswordKey.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2019 KeePassXC Team * * 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 @@ -16,35 +16,51 @@ */ #include "PasswordKey.h" +#include "core/Tools.h" #include "crypto/CryptoHash.h" +#include +#include +#include QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); +constexpr int PasswordKey::SHA256_SIZE; + PasswordKey::PasswordKey() : Key(UUID) + , m_key(static_cast(gcry_malloc_secure(SHA256_SIZE))) { } PasswordKey::PasswordKey(const QString& password) : Key(UUID) + , m_key(static_cast(gcry_malloc_secure(SHA256_SIZE))) { setPassword(password); } +PasswordKey::~PasswordKey() +{ + if (m_key) { + gcry_free(m_key); + m_key = nullptr; + } +} + QSharedPointer PasswordKey::fromRawKey(const QByteArray& rawKey) { auto result = QSharedPointer::create(); - result->m_key = rawKey; + std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size())); return result; } QByteArray PasswordKey::rawKey() const { - return m_key; + return QByteArray::fromRawData(m_key, SHA256_SIZE); } void PasswordKey::setPassword(const QString& password) { - m_key = CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256); + std::memcpy(m_key, CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256).data(), SHA256_SIZE); } diff --git a/src/keys/PasswordKey.h b/src/keys/PasswordKey.h index 68ab79895..4408cabcf 100644 --- a/src/keys/PasswordKey.h +++ b/src/keys/PasswordKey.h @@ -30,13 +30,16 @@ public: PasswordKey(); explicit PasswordKey(const QString& password); + ~PasswordKey() override; QByteArray rawKey() const override; void setPassword(const QString& password); static QSharedPointer fromRawKey(const QByteArray& rawKey); private: - QByteArray m_key; + static constexpr int SHA256_SIZE = 32; + + char* m_key = nullptr; }; #endif // KEEPASSX_PASSWORDKEY_H diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index f9cbe3174..759d8d1bc 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -1,6 +1,6 @@ /* + * Copyright (C) 2019 KeePassXC Team * Copyright (C) 2014 Kyle Manna - * Copyright (C) 2017 KeePassXC Team * * 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 @@ -32,6 +32,10 @@ #include #include +#include +#include +#include + QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508"); YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) @@ -45,9 +49,18 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) } } +YkChallengeResponseKey::~YkChallengeResponseKey() +{ + if (m_key) { + gcry_free(m_key); + m_keySize = 0; + m_key = nullptr; + } +} + QByteArray YkChallengeResponseKey::rawKey() const { - return m_key; + return QByteArray::fromRawData(m_key, static_cast(m_keySize)); } /** @@ -67,14 +80,22 @@ bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int emit userInteractionRequired(); } + QByteArray key; auto result = AsyncTask::runAndWaitForFuture( - [this, challenge]() { return YubiKey::instance()->challenge(m_slot, true, challenge, m_key); }); + [this, challenge, &key]() { return YubiKey::instance()->challenge(m_slot, true, challenge, key); }); if (m_blocking) { emit userConfirmed(); } if (result == YubiKey::SUCCESS) { + if (m_key) { + gcry_free(m_key); + } + m_keySize = static_cast(key.size()); + m_key = static_cast(gcry_malloc_secure(m_keySize)); + std::memcpy(m_key, key.data(), m_keySize); + sodium_memzero(key.data(), static_cast(key.capacity())); return true; } } while (retries > 0); diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index b8467e7a6..5f7c40e72 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2019 KeePassXC Team * * 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 @@ -32,6 +32,7 @@ public: static QUuid UUID; explicit YkChallengeResponseKey(int slot = -1, bool blocking = false); + ~YkChallengeResponseKey() override; QByteArray rawKey() const override; bool challenge(const QByteArray& challenge) override; @@ -52,7 +53,8 @@ signals: void userConfirmed(); private: - QByteArray m_key; + char* m_key = nullptr; + std::size_t m_keySize = 0; int m_slot; bool m_blocking; }; diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index ff645dadb..bdbfa3b74 100755 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -18,12 +18,13 @@ if(WITH_XC_BROWSER) include_directories(${BROWSER_SOURCE_DIR}) set(proxy_SOURCES + ../core/Alloc.cpp keepassxc-proxy.cpp ${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp NativeMessagingHost.cpp) add_library(proxy STATIC ${proxy_SOURCES}) - target_link_libraries(proxy Qt5::Core Qt5::Network) + target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE}) add_executable(keepassxc-proxy keepassxc-proxy.cpp) target_link_libraries(keepassxc-proxy proxy) diff --git a/src/touchid/TouchID.mm b/src/touchid/TouchID.mm index 9ef72189b..7df5ad556 100644 --- a/src/touchid/TouchID.mm +++ b/src/touchid/TouchID.mm @@ -15,6 +15,7 @@ inline void debug(const char* message, ...) { + Q_UNUSED(message); // qWarning(...); } @@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const NSString* authMessage = msg.toNSString(); // autoreleased [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:authMessage reply:^(BOOL success, NSError* error) { + Q_UNUSED(error); result = success ? kTouchIDResultAllowed : kTouchIDResultFailed; CFRunLoopWakeUp(CFRunLoopGetCurrent()); }]; From d3a53a702ecbc36ebfb7192a91be4a7e608a5d69 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 25 Apr 2019 09:28:48 +0200 Subject: [PATCH 17/24] Set console code page to CP_UTF8 on Windows if supported. (#3050) Previously, we enforced code page 850 for all console input and output, which breaks with non-western scripts. Since more recent Windows shells are able to display Unicode properly, this patch now enforces UTF-8 and falls back to code page 850 only if UTF-8 is unsupported. Non-Windows systems default to UTF-8, but can override the codec by setting the LANG environment variable to something other than C. Resolves #3049. --- src/cli/TextStream.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/cli/TextStream.cpp b/src/cli/TextStream.cpp index d75cb74a9..938fd6292 100644 --- a/src/cli/TextStream.cpp +++ b/src/cli/TextStream.cpp @@ -19,6 +19,9 @@ #include #include +#ifdef Q_OS_WIN +#include +#endif TextStream::TextStream() { @@ -59,12 +62,26 @@ void TextStream::detectCodec() { QString codecName = "UTF-8"; auto env = QProcessEnvironment::systemEnvironment(); + #ifdef Q_OS_WIN - if (!env.contains("SHELL")) { - // native shell (no Msys or cygwin) + WINBOOL success = false; +#ifdef CP_UTF8 + success = SetConsoleOutputCP(CP_UTF8); +#endif + if (!success && !env.contains("SHELL")) { + // Fall back to cp850 if this is Windows without CP_UTF8 and we + // are running in a native shell (i.e., no Msys or Cygwin). codecName = "Windows-850"; } +#else + if (env.contains("LANG") && !env.value("LANG").isEmpty() && env.value("LANG") != "C") { + // Only override codec if LANG is set, otherwise Qt will assume + // US-ASCII, which is almost always wrong and results in + // Unicode passwords being displayed as question marks. + codecName = QTextCodec::codecForLocale()->name(); + } #endif + codecName = env.value("ENCODING_OVERRIDE", codecName); auto* codec = QTextCodec::codecForName(codecName.toLatin1()); if (codec) { From a2caa31eca5632b64365e18a0a92e7dfb7d0f359 Mon Sep 17 00:00:00 2001 From: Oirio Joshi Date: Thu, 25 Apr 2019 16:39:06 +0200 Subject: [PATCH 18/24] Snap: fix theming (#3057) Use gtk3 file chooser dialogs, mouse coursor theme if available and force fallback icon theme, fixes issue #2966 --- snap/local/launchers/README.md | 11 ++++++++++ snap/local/launchers/gtk3-env-launch | 14 +++++++++++++ snapcraft.yaml => snap/snapcraft.yaml | 30 ++++++++++++++++++++++----- src/core/Bootstrap.cpp | 6 ++++++ 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 snap/local/launchers/README.md create mode 100755 snap/local/launchers/gtk3-env-launch rename snapcraft.yaml => snap/snapcraft.yaml (77%) diff --git a/snap/local/launchers/README.md b/snap/local/launchers/README.md new file mode 100644 index 000000000..334fbbdcc --- /dev/null +++ b/snap/local/launchers/README.md @@ -0,0 +1,11 @@ +# /snap/local/launchers +Here are the launchers, or wrapper programs to deal with some runtime-fixable problems for the snapped applications, like setting proper environmental variables in snap. + +In convention launchers are named _something_-launch, for dealing certain problem with _something_, and usually can be called in a stacked manner to consolidate their modifications. + +```yaml +apps: + _app_name_: + command: foo-launch bar-launch _app_command_ +``` + diff --git a/snap/local/launchers/gtk3-env-launch b/snap/local/launchers/gtk3-env-launch new file mode 100755 index 000000000..f017e8611 --- /dev/null +++ b/snap/local/launchers/gtk3-env-launch @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# This is the maintainence launcher for the snap, make necessary runtime environment changes to make the snap work here. You may also insert security confinement/deprecation/obsoletion notice of the snap here. + +set \ + -o errexit \ + -o errtrace \ + -o nounset \ + -o pipefail + +# gtk-common-themes support +export QT_QPA_PLATFORMTHEME=gtk3 + +# Finally run the next part of the command chain +exec "${@}" diff --git a/snapcraft.yaml b/snap/snapcraft.yaml similarity index 77% rename from snapcraft.yaml rename to snap/snapcraft.yaml index 0aafd7a19..42ac80c43 100644 --- a/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -9,15 +9,27 @@ description: | confinement: strict base: core18 -plugs: - icon-themes: # fix mouse cursor theme +plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser + gtk-3-themes: + interface: content + target: $SNAP/data-dir/themes + default-provider: gtk-common-themes:gtk-3-themes + icon-themes: interface: content target: $SNAP/data-dir/icons - default-provider: gtk-common-themes + default-provider: gtk-common-themes:icon-themes + sound-themes: + interface: content + target: $SNAP/data-dir/sounds + default-provider: gtk-common-themes:sounds-themes apps: keepassxc: - command: desktop-launch keepassxc + adapter: full + command: usr/bin/keepassxc -style fusion + command-chain: + - bin/desktop-launch + - bin/gtk3-env-launch plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy, desktop] desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop environment: @@ -73,7 +85,7 @@ parts: - libquazip5-1 - libusb-1.0-0 - qtwayland5 - - qt5-style-plugins # for mouse cursor theme fix + - qt5-gtk-platformtheme # for theming, font settings, cursor and to use gtk3 file chooser override-build: | snapcraftctl build sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop @@ -83,6 +95,14 @@ parts: - -opt after: [desktop-qt5] + launchers: # custom launcher to set QT_QPA_PLATFORMTHEME=gtk3 correctly + source: snap/local/launchers + plugin: dump + organize: + '*': bin/ + stage: + - -bin/README.* + desktop-qt5: source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source-subdir: qt diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp index a06bf74c1..2d1a3e087 100644 --- a/src/core/Bootstrap.cpp +++ b/src/core/Bootstrap.cpp @@ -85,6 +85,12 @@ namespace Bootstrap bootstrap(); MessageBox::initializeButtonDefs(); +#ifdef KEEPASSXC_DIST_SNAP + // snap: force fallback theme to avoid using system theme (gtk integration) + // with missing actions just like on Windows and macOS + QIcon::setThemeSearchPaths(QStringList() << ":/icons"); +#endif + #ifdef Q_OS_MACOS // Don't show menu icons on OSX QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); From 0f8d2986af765d08e39907c7262423100d86dda9 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sat, 27 Apr 2019 13:37:42 +0300 Subject: [PATCH 19/24] Close popups when database is locked --- src/browser/BrowserService.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 9c06c2487..112a7cda9 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -296,6 +296,7 @@ QString BrowserService::storeKey(const QString& key) do { QInputDialog keyDialog; + connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject())); keyDialog.setWindowTitle(tr("KeePassXC: New key association request")); keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n" "If you would like to allow it access to your KeePassXC database,\n" @@ -310,7 +311,7 @@ QString BrowserService::storeKey(const QString& key) id = keyDialog.textValue(); - if (ok != QDialog::Accepted || id.isEmpty()) { + if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) { hideWindow(); return {}; } @@ -406,6 +407,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id, return QJsonArray(); } + // Ensure that database is not locked when the popup was visible + if (!isDatabaseOpened()) { + return QJsonArray(); + } + // Sort results pwEntries = sortEntries(pwEntries, host, submitUrl); @@ -760,6 +766,7 @@ bool BrowserService::confirmEntries(QList& pwEntriesToConfirm, m_dialogActive = true; BrowserAccessControlDialog accessControlDialog; + connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject())); accessControlDialog.setUrl(url); accessControlDialog.setItems(pwEntriesToConfirm); From 01a3d5b0ba0047b3179f44dd5caba74055228219 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 27 Apr 2019 00:43:43 +0200 Subject: [PATCH 20/24] Fix QuaZip find module on macOS and clean up code. Finding libquazip failed on macOS due to path differences. This patch also cleans up the find module's code, aligns it with the coding style of the other CMake files and removes clutter that is not needed for KeePassXC such as non-Msys builds on Windows. --- cmake/FindQuaZip.cmake | 59 ++++++++++++---------------------- src/keeshare/ShareObserver.cpp | 4 +-- 2 files changed, 23 insertions(+), 40 deletions(-) diff --git a/cmake/FindQuaZip.cmake b/cmake/FindQuaZip.cmake index 8d3091810..a387e2f81 100644 --- a/cmake/FindQuaZip.cmake +++ b/cmake/FindQuaZip.cmake @@ -1,41 +1,24 @@ -# QUAZIP_FOUND - QuaZip library was found -# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir -# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR) -# QUAZIP_LIBRARIES - List of QuaZip libraries -# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers +# QUAZIP_FOUND - QuaZip library was found +# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir +# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR) +# QUAZIP_LIBRARIES - List of QuaZip libraries +# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers -IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) - # in cache already - SET(QUAZIP_FOUND TRUE) -ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) - IF(Qt5Core_FOUND) - set(QUAZIP_LIB_VERSION_SUFFIX 5) - ENDIF() - IF(WIN32) - FIND_PATH(QUAZIP_LIBRARY_DIR - WIN32_DEBUG_POSTFIX d - NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll - HINTS "C:/Programme/" "C:/Program Files" - PATH_SUFFIXES QuaZip/lib +if(MINGW) + find_library(QUAZIP_LIBRARIES libquazip5) + find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5) + find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h) +else() + find_library(QUAZIP_LIBRARIES + NAMES quazip5 quazip + PATHS /usr/lib /usr/lib64 /usr/local/lib ) - FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR}) - FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5) - FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h) - ELSE(WIN32) - FIND_PACKAGE(PkgConfig) - pkg_check_modules(PC_QUAZIP quazip) - FIND_LIBRARY(QUAZIP_LIBRARIES - WIN32_DEBUG_POSTFIX d - NAMES quazip${QUAZIP_LIB_VERSION_SUFFIX} - HINTS /usr/lib /usr/lib64 + find_path(QUAZIP_INCLUDE_DIR quazip.h + PATHS /usr/include /usr/local/include + PATH_SUFFIXES quazip5 quazip ) - FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h - HINTS /usr/include /usr/local/include - PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX} - ) - FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include) - ENDIF(WIN32) - INCLUDE(FindPackageHandleStandardArgs) - SET(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR}) - find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS) -ENDIF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) + find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include) +endif() +include(FindPackageHandleStandardArgs) +set(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR}) +find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS) diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp index 33f5ed1f6..644f1c157 100644 --- a/src/keeshare/ShareObserver.cpp +++ b/src/keeshare/ShareObserver.cpp @@ -46,8 +46,8 @@ #include #if defined(WITH_XC_KEESHARE_SECURE) -#include -#include +#include +#include #endif namespace From e4eee897f9fb931d8081f9b70da0b0f1e6a8b9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20V=C3=A4nttinen?= Date: Thu, 2 May 2019 01:35:08 +0300 Subject: [PATCH 21/24] Support Database Custom Data Merging (#3002) * Introduce _LAST_MODIFIED custom data entry that stores the last modified datetime of the database's custom data entries * Merge custom data from source database to target * Modify tests to be aware of _LAST_MODIFIED entry --- src/core/CustomData.cpp | 26 ++++++++++++++++++ src/core/CustomData.h | 7 +++++ src/core/Merger.cpp | 30 ++++++++++++++++++--- tests/TestKdbx4.cpp | 20 ++++++++------ tests/TestMerge.cpp | 59 +++++++++++++++++++++++++++++++++++++++++ tests/TestMerge.h | 1 + 6 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/core/CustomData.cpp b/src/core/CustomData.cpp index 86adae158..f009176a0 100644 --- a/src/core/CustomData.cpp +++ b/src/core/CustomData.cpp @@ -16,9 +16,12 @@ */ #include "CustomData.h" +#include "Clock.h" #include "core/Global.h" +const QString CustomData::LastModified = "_LAST_MODIFIED"; + CustomData::CustomData(QObject* parent) : QObject(parent) { @@ -60,6 +63,7 @@ void CustomData::set(const QString& key, const QString& value) if (addAttribute || changeValue) { m_data.insert(key, value); + updateLastModified(); emit customDataModified(); } @@ -74,6 +78,7 @@ void CustomData::remove(const QString& key) m_data.remove(key); + updateLastModified(); emit removed(key); emit customDataModified(); } @@ -94,6 +99,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey) m_data.remove(oldKey); m_data.insert(newKey, data); + updateLastModified(); emit customDataModified(); emit renamed(oldKey, newKey); } @@ -108,9 +114,19 @@ void CustomData::copyDataFrom(const CustomData* other) m_data = other->m_data; + updateLastModified(); emit reset(); emit customDataModified(); } + +QDateTime CustomData::getLastModified() const +{ + if (m_data.contains(LastModified)) { + return Clock::parse(m_data.value(LastModified)); + } + return {}; +} + bool CustomData::operator==(const CustomData& other) const { return (m_data == other.m_data); @@ -152,3 +168,13 @@ int CustomData::dataSize() const } return size; } + +void CustomData::updateLastModified() +{ + if (m_data.size() == 1 && m_data.contains(LastModified)) { + m_data.remove(LastModified); + return; + } + + m_data.insert(LastModified, Clock::currentDateTimeUtc().toString()); +} diff --git a/src/core/CustomData.h b/src/core/CustomData.h index d085c9409..126d4d84e 100644 --- a/src/core/CustomData.h +++ b/src/core/CustomData.h @@ -42,9 +42,12 @@ public: int size() const; int dataSize() const; void copyDataFrom(const CustomData* other); + QDateTime getLastModified() const; bool operator==(const CustomData& other) const; bool operator!=(const CustomData& other) const; + static const QString LastModified; + signals: void customDataModified(); void aboutToBeAdded(const QString& key); @@ -55,6 +58,10 @@ signals: void renamed(const QString& oldKey, const QString& newKey); void aboutToBeReset(); void reset(); + void lastModified(); + +private slots: + void updateLastModified(); private: QHash m_data; diff --git a/src/core/Merger.cpp b/src/core/Merger.cpp index c73248388..dcbed250f 100644 --- a/src/core/Merger.cpp +++ b/src/core/Merger.cpp @@ -609,9 +609,6 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context) // TODO HNH: missing handling of recycle bin, names, templates for groups and entries, // public data (entries of newer dict override keys of older dict - ignoring // their own age - it is enough if one entry of the whole dict is newer) => possible lost update - // TODO HNH: CustomData is merged with entries of the new customData overwrite entries - // of the older CustomData - the dict with the newest entry is considered - // newer regardless of the age of the other entries => possible lost update ChangeList changes; auto* sourceMetadata = context.m_sourceDb->metadata(); auto* targetMetadata = context.m_targetDb->metadata(); @@ -624,5 +621,32 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context) changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex())); } } + + // Merge Custom Data if source is newer + const auto targetCustomDataModificationTime = sourceMetadata->customData()->getLastModified(); + const auto sourceCustomDataModificationTime = targetMetadata->customData()->getLastModified(); + if (!targetMetadata->customData()->contains(CustomData::LastModified) || + (targetCustomDataModificationTime.isValid() && sourceCustomDataModificationTime.isValid() && + targetCustomDataModificationTime > sourceCustomDataModificationTime)) { + const auto sourceCustomDataKeys = sourceMetadata->customData()->keys(); + const auto targetCustomDataKeys = targetMetadata->customData()->keys(); + + // Check missing keys from source. Remove those from target + for (const auto& key : targetCustomDataKeys) { + if (!sourceMetadata->customData()->contains(key)) { + auto value = targetMetadata->customData()->value(key); + targetMetadata->customData()->remove(key); + changes << tr("Removed custom data %1 [%2]").arg(key, value); + } + } + + // Transfer new/existing keys + for (const auto& key : sourceCustomDataKeys) { + auto value = sourceMetadata->customData()->value(key); + targetMetadata->customData()->set(key, value); + changes << tr("Adding custom data %1 [%2]").arg(key, value); + } + } + return changes; } diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp index fe4679da5..88352d825 100644 --- a/tests/TestKdbx4.cpp +++ b/tests/TestKdbx4.cpp @@ -186,8 +186,10 @@ void TestKdbx4::testFormat400Upgrade() QCOMPARE(reader.version(), expectedVersion); QCOMPARE(targetDb->cipher(), cipherUuid); - QCOMPARE(*targetDb->metadata()->customData(), *sourceDb->metadata()->customData()); - QCOMPARE(*targetDb->rootGroup()->customData(), *sourceDb->rootGroup()->customData()); + QCOMPARE(targetDb->metadata()->customData()->value("CustomPublicData"), + sourceDb->metadata()->customData()->value("CustomPublicData")); + QCOMPARE(targetDb->rootGroup()->customData()->value("CustomGroupData"), + sourceDb->rootGroup()->customData()->value("CustomGroupData")); } // clang-format off @@ -346,20 +348,22 @@ void TestKdbx4::testCustomData() const QString customDataKey2 = "CD2"; const QString customData1 = "abcäöü"; const QString customData2 = "Hello World"; - const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size() - + customData2.toUtf8().size(); // test custom database data db.metadata()->customData()->set(customDataKey1, customData1); db.metadata()->customData()->set(customDataKey2, customData2); - QCOMPARE(db.metadata()->customData()->size(), 2); + auto lastModified = db.metadata()->customData()->value(CustomData::LastModified); + const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size() + + customData2.toUtf8().size() + lastModified.toUtf8().size() + + CustomData::LastModified.toUtf8().size(); + QCOMPARE(db.metadata()->customData()->size(), 3); QCOMPARE(db.metadata()->customData()->dataSize(), dataSize); // test custom root group data Group* root = db.rootGroup(); root->customData()->set(customDataKey1, customData1); root->customData()->set(customDataKey2, customData2); - QCOMPARE(root->customData()->size(), 2); + QCOMPARE(root->customData()->size(), 3); QCOMPARE(root->customData()->dataSize(), dataSize); // test copied custom group data @@ -378,9 +382,9 @@ void TestKdbx4::testCustomData() // test custom data deletion entry->customData()->set("additional item", "foobar"); - QCOMPARE(entry->customData()->size(), 3); + QCOMPARE(entry->customData()->size(), 4); entry->customData()->remove("additional item"); - QCOMPARE(entry->customData()->size(), 2); + QCOMPARE(entry->customData()->size(), 3); QCOMPARE(entry->customData()->dataSize(), dataSize); // test custom data on cloned groups diff --git a/tests/TestMerge.cpp b/tests/TestMerge.cpp index 03eae32ef..4d9aef211 100644 --- a/tests/TestMerge.cpp +++ b/tests/TestMerge.cpp @@ -1164,6 +1164,65 @@ void TestMerge::testMetadata() // will be used - exception is the target has no recycle bin activated } +void TestMerge::testCustomdata() +{ + QScopedPointer dbDestination(new Database()); + QScopedPointer dbSource(createTestDatabase()); + QScopedPointer dbDestination2(new Database()); + QScopedPointer dbSource2(createTestDatabase()); + + m_clock->advanceSecond(1); + + dbDestination->metadata()->customData()->set("toBeDeleted", "value"); + dbDestination->metadata()->customData()->set("key3", "oldValue"); + + dbSource2->metadata()->customData()->set("key1", "value1"); + dbSource2->metadata()->customData()->set("key2", "value2"); + dbSource2->metadata()->customData()->set("key3", "newValue"); + dbSource2->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]"); + + m_clock->advanceSecond(1); + + dbSource->metadata()->customData()->set("key1", "value1"); + dbSource->metadata()->customData()->set("key2", "value2"); + dbSource->metadata()->customData()->set("key3", "newValue"); + dbSource->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]"); + + dbDestination2->metadata()->customData()->set("notToBeDeleted", "value"); + dbDestination2->metadata()->customData()->set("key3", "oldValue"); + + // Sanity check. + QVERIFY(!dbSource->metadata()->customData()->isEmpty()); + QVERIFY(!dbSource2->metadata()->customData()->isEmpty()); + + m_clock->advanceSecond(1); + + Merger merger(dbSource.data(), dbDestination.data()); + merger.merge(); + + Merger merger2(dbSource2.data(), dbDestination2.data()); + merger2.merge(); + + // Source is newer, data should be merged + QVERIFY(!dbDestination->metadata()->customData()->isEmpty()); + QVERIFY(dbDestination->metadata()->customData()->contains("key1")); + QVERIFY(dbDestination->metadata()->customData()->contains("key2")); + QVERIFY(dbDestination->metadata()->customData()->contains("Browser")); + QVERIFY(!dbDestination->metadata()->customData()->contains("toBeDeleted")); + QCOMPARE(dbDestination->metadata()->customData()->value("key1"), QString("value1")); + QCOMPARE(dbDestination->metadata()->customData()->value("key2"), QString("value2")); + QCOMPARE(dbDestination->metadata()->customData()->value("Browser"), QString("n'8=3W@L^6d->d.]St_>]")); + QCOMPARE(dbDestination->metadata()->customData()->value("key3"), QString("newValue")); // Old value should be replaced + + // Target is newer, no data is merged + QVERIFY(!dbDestination2->metadata()->customData()->isEmpty()); + QVERIFY(!dbDestination2->metadata()->customData()->contains("key1")); + QVERIFY(!dbDestination2->metadata()->customData()->contains("key2")); + QVERIFY(!dbDestination2->metadata()->customData()->contains("Browser")); + QVERIFY(dbDestination2->metadata()->customData()->contains("notToBeDeleted")); + QCOMPARE(dbDestination2->metadata()->customData()->value("key3"), QString("oldValue")); // Old value should not be replaced +} + void TestMerge::testDeletedEntry() { QScopedPointer dbDestination(createTestDatabase()); diff --git a/tests/TestMerge.h b/tests/TestMerge.h index 357f85262..15f67ca79 100644 --- a/tests/TestMerge.h +++ b/tests/TestMerge.h @@ -59,6 +59,7 @@ private slots: void testMergeCustomIcons(); void testMergeDuplicateCustomIcons(); void testMetadata(); + void testCustomdata(); void testDeletedEntry(); void testDeletedGroup(); void testDeletedRevertedEntry(); From ebe66496834ea20ad5bb1d76f67c84c26d086550 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sun, 28 Apr 2019 11:08:20 +0300 Subject: [PATCH 22/24] Lock database on switching user in macOS --- src/CMakeLists.txt | 3 ++- src/gui/DatabaseTabWidget.cpp | 4 +++ src/gui/macutils/AppKit.h | 14 +++++----- src/gui/macutils/AppKitImpl.h | 5 ++++ src/gui/macutils/AppKitImpl.mm | 47 ++++++++++++++++++++++++++-------- src/gui/macutils/MacUtils.cpp | 2 +- src/gui/macutils/MacUtils.h | 6 +++-- 7 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16d0ef89c..5e7a474d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -168,7 +168,8 @@ if(APPLE) core/ScreenLockListenerMac.cpp core/MacPasteboard.cpp gui/macutils/MacUtils.cpp - gui/macutils/AppKitImpl.mm) + gui/macutils/AppKitImpl.mm + gui/macutils/AppKit.h) endif() if(UNIX AND NOT APPLE) set(keepassx_SOURCES diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 313bfabb1..fb234795c 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -63,6 +63,10 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase())); // clang-format on + +#ifdef Q_OS_MACOS + connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases())); +#endif } DatabaseTabWidget::~DatabaseTabWidget() diff --git a/src/gui/macutils/AppKit.h b/src/gui/macutils/AppKit.h index cdb822ffc..da81f6913 100644 --- a/src/gui/macutils/AppKit.h +++ b/src/gui/macutils/AppKit.h @@ -19,14 +19,15 @@ #ifndef KEEPASSX_APPKIT_H #define KEEPASSX_APPKIT_H +#include #include -extern "C" { - -class AppKit +class AppKit : public QObject { + Q_OBJECT + public: - AppKit(); + AppKit(QObject* parent = nullptr); ~AppKit(); pid_t lastActiveProcessId(); @@ -37,10 +38,11 @@ public: bool isHidden(pid_t pid); bool isDarkMode(); +signals: + void lockDatabases(); + private: void *self; }; -} // extern "C" - #endif // KEEPASSX_APPKIT_H diff --git a/src/gui/macutils/AppKitImpl.h b/src/gui/macutils/AppKitImpl.h index 3bf2d20ef..ca2506794 100644 --- a/src/gui/macutils/AppKitImpl.h +++ b/src/gui/macutils/AppKitImpl.h @@ -22,6 +22,10 @@ #import @interface AppKitImpl : NSObject +{ + AppKit *m_appkit; +} +- (id) initWithObject:(AppKit *)appkit; @property (strong) NSRunningApplication *lastActiveApplication; @@ -31,5 +35,6 @@ - (bool) hideProcess:(pid_t) pid; - (bool) isHidden:(pid_t) pid; - (bool) isDarkMode; +- (void) userSwitchHandler:(NSNotification*) notification; @end diff --git a/src/gui/macutils/AppKitImpl.mm b/src/gui/macutils/AppKitImpl.mm index cd709df27..4165e0d5e 100644 --- a/src/gui/macutils/AppKitImpl.mm +++ b/src/gui/macutils/AppKitImpl.mm @@ -22,19 +22,22 @@ @implementation AppKitImpl -AppKit::AppKit() +- (id) initWithObject:(AppKit *)appkit { - self = [[AppKitImpl alloc] init]; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast(self) + self = [super init]; + if (self) { + m_appkit = appkit; + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast(self) selector:@selector(didDeactivateApplicationObserver:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil]; -} - -AppKit::~AppKit() -{ - [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast(self)]; - [static_cast(self) dealloc]; + + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast(self) + selector:@selector(userSwitchHandler:) + name:NSWorkspaceSessionDidResignActiveNotification + object:nil]; + } + return self; } // @@ -104,10 +107,34 @@ AppKit::~AppKit() && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] ); } +// +// Notification for user switch +// +- (void) userSwitchHandler:(NSNotification*) notification +{ + if ([[notification name] isEqualToString:NSWorkspaceSessionDidResignActiveNotification] && m_appkit) + { + emit m_appkit->lockDatabases(); + } +} + +@end + // // ------------------------- C++ Trampolines ------------------------- // +AppKit::AppKit(QObject* parent) : QObject(parent) +{ + self = [[AppKitImpl alloc] initWithObject:this]; +} + +AppKit::~AppKit() +{ + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast(self)]; + [static_cast(self) dealloc]; +} + pid_t AppKit::lastActiveProcessId() { return [static_cast(self) lastActiveApplication].processIdentifier; @@ -142,5 +169,3 @@ bool AppKit::isDarkMode() { return [static_cast(self) isDarkMode]; } - -@end diff --git a/src/gui/macutils/MacUtils.cpp b/src/gui/macutils/MacUtils.cpp index c362fe1bd..654923c31 100644 --- a/src/gui/macutils/MacUtils.cpp +++ b/src/gui/macutils/MacUtils.cpp @@ -24,7 +24,7 @@ MacUtils* MacUtils::m_instance = nullptr; MacUtils::MacUtils(QObject* parent) : QObject(parent) , m_appkit(new AppKit()) { - + connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases())); } MacUtils::~MacUtils() diff --git a/src/gui/macutils/MacUtils.h b/src/gui/macutils/MacUtils.h index 39a06bd84..49644795e 100644 --- a/src/gui/macutils/MacUtils.h +++ b/src/gui/macutils/MacUtils.h @@ -39,14 +39,16 @@ public: bool isHidden(); bool isDarkMode(); +signals: + void lockDatabases(); + private: explicit MacUtils(QObject* parent = nullptr); ~MacUtils(); private: - std::unique_ptr m_appkit; + QScopedPointer m_appkit; static MacUtils* m_instance; - void* self; Q_DISABLE_COPY(MacUtils) }; From faf7a2bbb3b00bcc8fbe66c627dd2d1128d94aa8 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 3 May 2019 14:40:45 -0400 Subject: [PATCH 23/24] Robust processing of tray icon triggers * Support double click on tray icon to always toggle window to/from tray * Single click on tray icon will bring window to front if in background, otherwise window is toggled * Fixes #2956 --- src/gui/MainWindow.cpp | 39 +++++++++++++++++++++++++++++++++++++-- src/gui/MainWindow.h | 5 +++++ tests/gui/TestGui.cpp | 19 ++++++++++++++----- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 96475a0fb..e5f5ea613 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -139,6 +139,7 @@ MainWindow::MainWindow() , m_trayIcon(nullptr) , m_appExitCalled(false) , m_appExiting(false) + , m_lastFocusOutTime(0) { g_MainWindow = this; @@ -403,6 +404,12 @@ MainWindow::MainWindow() connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock())); #endif + // Tray Icon setup + connect(Application::instance(), SIGNAL(focusWindowChanged(QWindow*)), SLOT(focusWindowChanged(QWindow*))); + m_trayIconTriggerReason = QSystemTrayIcon::Unknown; + m_trayIconTriggerTimer.setSingleShot(true); + connect(&m_trayIconTriggerTimer, SIGNAL(timeout()), SLOT(processTrayIconTrigger())); + updateTrayIcon(); if (config()->hasAccessError()) { @@ -919,7 +926,7 @@ bool MainWindow::saveLastDatabases() } QStringList openDatabases; - for (int i=0; i < m_ui->tabWidget->count(); ++i) { + for (int i = 0; i < m_ui->tabWidget->count(); ++i) { auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i); openDatabases.append(dbWidget->database()->filePath()); } @@ -1038,10 +1045,38 @@ void MainWindow::applySettingsChanges() updateTrayIcon(); } +void MainWindow::focusWindowChanged(QWindow* focusWindow) +{ + if (focusWindow != windowHandle()) { + m_lastFocusOutTime = Clock::currentSecondsSinceEpoch(); + } +} + void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) { - if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::MiddleClick) { + if (!m_trayIconTriggerTimer.isActive()) { + m_trayIconTriggerTimer.start(150); + } + // Overcome Qt bug https://bugreports.qt.io/browse/QTBUG-69698 + // Store last issued tray icon activation reason to properly + // capture doubleclick events + m_trayIconTriggerReason = reason; +} + +void MainWindow::processTrayIconTrigger() +{ + if (m_trayIconTriggerReason == QSystemTrayIcon::DoubleClick) { + // Always toggle window on double click toggleWindow(); + } else if (m_trayIconTriggerReason == QSystemTrayIcon::Trigger + || m_trayIconTriggerReason == QSystemTrayIcon::MiddleClick) { + // On single/middle click focus the window if it is not hidden + // and did not have focus less than a second ago, otherwise toggle + if (isHidden() || (Clock::currentSecondsSinceEpoch() - m_lastFocusOutTime) <= 1) { + toggleWindow(); + } else { + bringToFront(); + } } } diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 5a72d6f02..f1e543468 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -85,6 +85,7 @@ private slots: void showAboutDialog(); void showUpdateCheckStartup(); void showUpdateCheckDialog(); + void focusWindowChanged(QWindow* focusWindow); void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested); void openDonateUrl(); void openBugReportUrl(); @@ -107,6 +108,7 @@ private slots: void showGroupContextMenu(const QPoint& globalPos); void applySettingsChanges(); void trayIconTriggered(QSystemTrayIcon::ActivationReason reason); + void processTrayIconTrigger(); void lockDatabasesAfterInactivity(); void forgetTouchIDAfterInactivity(); void handleScreenLock(); @@ -146,6 +148,9 @@ private: bool m_appExitCalled; bool m_appExiting; + uint m_lastFocusOutTime; + QTimer m_trayIconTriggerTimer; + QSystemTrayIcon::ActivationReason m_trayIconTriggerReason; }; /** diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 63e36fbaa..0db2a5dfb 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -1286,11 +1286,11 @@ void TestGui::testTrayRestoreHide() QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test..."); } + m_mainWindow->hideWindow(); + QVERIFY(!m_mainWindow->isVisible()); + auto* trayIcon = m_mainWindow->findChild(); - QVERIFY(m_mainWindow->isVisible()); - - trayIcon->activated(QSystemTrayIcon::Trigger); - QTRY_VERIFY(!m_mainWindow->isVisible()); + QVERIFY(trayIcon); trayIcon->activated(QSystemTrayIcon::Trigger); QTRY_VERIFY(m_mainWindow->isVisible()); @@ -1298,8 +1298,17 @@ void TestGui::testTrayRestoreHide() trayIcon->activated(QSystemTrayIcon::Trigger); QTRY_VERIFY(!m_mainWindow->isVisible()); - trayIcon->activated(QSystemTrayIcon::Trigger); + trayIcon->activated(QSystemTrayIcon::MiddleClick); QTRY_VERIFY(m_mainWindow->isVisible()); + + trayIcon->activated(QSystemTrayIcon::MiddleClick); + QTRY_VERIFY(!m_mainWindow->isVisible()); + + trayIcon->activated(QSystemTrayIcon::DoubleClick); + QTRY_VERIFY(m_mainWindow->isVisible()); + + trayIcon->activated(QSystemTrayIcon::DoubleClick); + QTRY_VERIFY(!m_mainWindow->isVisible()); } int TestGui::addCannedEntries() From 247b85fe69aea28a7d0f87ebc8aff9fc26361c8c Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 7 May 2019 12:56:55 -0400 Subject: [PATCH 24/24] Update INSTALL.md and cleanup CMakeLists.txt (#3074) INSTALL.md * Better organization of CMake options CMakeLists.txt * If WITH_XC_NETWORKING is disabled, also disable WITH_XC_UPDATECHECK * Move KeeShare logic into KeeShare CMakeLists.txt * Remove WITH_XC_KEESHARE_SECURE build option * Attempt to find quazip, if found enable WITH_XC_KEESHARE_SECURE and build with secure container support --- CMakeLists.txt | 30 +++++++----------------------- INSTALL.md | 14 +++++++++++--- src/CMakeLists.txt | 9 ++++----- src/keeshare/CMakeLists.txt | 20 ++++++++++++++++---- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a84ef18c..689515a39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,19 +44,18 @@ option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON) set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins") option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) -option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF) +option(WITH_XC_NETWORKING "Include networking code (e.g. for downloading website icons)." OFF) option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF) 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" OFF) -option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" 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(APPLE) option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) endif() if(WITH_XC_ALL) - # Enable all options + # Enable all options (except update check) set(WITH_XC_AUTOTYPE ON) set(WITH_XC_NETWORKING ON) set(WITH_XC_BROWSER ON) @@ -68,18 +67,16 @@ if(WITH_XC_ALL) endif() endif() -if(WITH_XC_KEESHARE_SECURE) - set(WITH_XC_KEESHARE ON) -endif() - if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE) set(WITH_XC_CRYPTO_SSH ON) else() set(WITH_XC_CRYPTO_SSH OFF) endif() -if(WITH_XC_UPDATECHECK) - set(WITH_XC_NETWORKING ON) +# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK +if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK) + message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled") + set(WITH_XC_UPDATECHECK OFF) endif() set(KEEPASSXC_VERSION_MAJOR "2") @@ -406,19 +403,6 @@ endif() include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR}) -# Optional -if(WITH_XC_KEESHARE) - set(WITH_XC_KEESHARE_INSECURE ON) - if(WITH_XC_KEESHARE_SECURE) - # ZLIB is needed and already required - find_package(QuaZip REQUIRED) - include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR}) - endif() -else() - set(WITH_XC_KEESHARE_INSECURE OFF) - set(WITH_XC_KEESHARE_SECURE OFF) -endif() - # Optional if(WITH_XC_YUBIKEY) find_package(YubiKey REQUIRED) diff --git a/INSTALL.md b/INSTALL.md index 3bc9185d9..957e6d37d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -97,18 +97,26 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct -DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON) -DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF) -DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF) - -DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (favicon download) (default: OFF) + -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_KEESHARE=[ON|OFF] Enable/Disable KeeShare group syncronization extension (default: OFF) -DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (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) - -DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare secure containers, requires libquazip5 (default: OFF) + + -DWITH_XC_UPDATECHECK=[ON|OFF] Enable/Disable automatic updating checking (requires WITH_XC_NETWORKING) (default: ON) + -DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON) -DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF) -DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF) -DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF) -DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF) -DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON) + + -DKEEPASSXC_BUILD_TYPE=[Snapshot|PreRelease|Release] Set the build type to show/hide stability warnings (default: "Snapshot") + -DKEEPASSXC_DIST_TYPE=[Snap|AppImage|Other] Specify the distribution method (default: "Other") + -DOVERRIDE_VERSION=[X.X.X] Specify a version number when building. Used with snapshot builds (default: "") + -DGIT_HEAD_OVERRIDE=[XXXXXXX] Specify the 7 digit git commit ref for this build. Used with distribution builds (default: "") ``` * If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed
`-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e7a474d2..f142f3680 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,9 +16,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) -configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h) -configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h) - find_library(ZXCVBN_LIBRARIES zxcvbn) if(NOT ZXCVBN_LIBRARIES) add_library(zxcvbn STATIC zxcvbn/zxcvbn.c) @@ -194,8 +191,7 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing") add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)") add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser") add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent") -add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare") -add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources") +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(APPLE) @@ -260,6 +256,9 @@ if(WITH_XC_TOUCHID) set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error") endif() +configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h) +configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h) + add_library(autotype STATIC ${autotype_SOURCES}) target_link_libraries(autotype Qt5::Core Qt5::Widgets) diff --git a/src/keeshare/CMakeLists.txt b/src/keeshare/CMakeLists.txt index 14aa17b99..d791d3be6 100644 --- a/src/keeshare/CMakeLists.txt +++ b/src/keeshare/CMakeLists.txt @@ -1,4 +1,6 @@ if(WITH_XC_KEESHARE) + set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE) + include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) set(keeshare_SOURCES @@ -15,9 +17,19 @@ if(WITH_XC_KEESHARE) ) add_library(keeshare STATIC ${keeshare_SOURCES}) - if(WITH_XC_KEESHARE_SECURE) - target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB}) + target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) + + # Try to find libquazip5, if found, enable secure sharing + find_package(QuaZip) + if(QUAZIP_FOUND) + set(WITH_XC_KEESHARE_SECURE ON PARENT_SCOPE) + target_include_directories(keeshare SYSTEM PRIVATE ${QUAZIP_INCLUDE_DIR}) + target_link_libraries(keeshare PRIVATE ${QUAZIP_LIBRARIES}) else() - target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) + set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE) + message(STATUS "KeeShare: Secure container support is DISABLED; quazip library not found") endif() -endif() +else(WITH_XC_KEESHARE) + set(WITH_XC_KEESHARE_INSECURE OFF PARENT_SCOPE) + set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE) +endif(WITH_XC_KEESHARE)