Merge branch 'develop'

Conflicts:
	src/core/Tools.cpp
	src/sshagent/SSHAgent.cpp
This commit is contained in:
Jonathan White 2018-12-30 16:32:57 -05:00
commit 21de6f6163
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
57 changed files with 1296 additions and 395 deletions

View File

@ -262,11 +262,6 @@ else()
set(PROGNAME keepassxc)
endif()
if(APPLE AND WITH_APP_BUNDLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local")
set(CMAKE_INSTALL_PREFIX "/Applications")
set(CMAKE_INSTALL_MANDIR "/usr/local/share/man")
endif()
if(MINGW)
set(CLI_INSTALL_DIR ".")
set(PROXY_INSTALL_DIR ".")
@ -274,9 +269,10 @@ if(MINGW)
set(PLUGIN_INSTALL_DIR ".")
set(DATA_INSTALL_DIR "share")
elseif(APPLE AND WITH_APP_BUNDLE)
set(CLI_INSTALL_DIR "/usr/local/bin")
set(PROXY_INSTALL_DIR "/usr/local/bin")
set(BIN_INSTALL_DIR ".")
set(CMAKE_INSTALL_MANDIR "${PROGNAME}.app/Contents/Resources/man")
set(CLI_INSTALL_DIR "${PROGNAME}.app/Contents/MacOS")
set(PROXY_INSTALL_DIR "${PROGNAME}.app/Contents/MacOS")
set(BIN_INSTALL_DIR "${PROGNAME}.app/Contents/MacOS")
set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns")
set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources")
else()
@ -314,8 +310,8 @@ set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools)
if(UNIX AND NOT APPLE)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED)
elseif(APPLE)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH)
find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH)
find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH)
else()
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED)
endif()

View File

@ -175,7 +175,9 @@ if(APPLE)
set(keepassx_SOURCES
${keepassx_SOURCES}
core/ScreenLockListenerMac.cpp
core/MacPasteboard.cpp)
core/MacPasteboard.cpp
gui/macutils/MacUtils.cpp
gui/macutils/AppKitImpl.mm)
endif()
if(UNIX AND NOT APPLE)
set(keepassx_SOURCES
@ -287,7 +289,7 @@ if(WITH_XC_KEESHARE)
endif()
if(APPLE)
target_link_libraries(keepassx_core "-framework Foundation")
target_link_libraries(keepassx_core "-framework Foundation -framework AppKit")
if(Qt5MacExtras_FOUND)
target_link_libraries(keepassx_core Qt5::MacExtras)
endif()

View File

@ -17,6 +17,7 @@
*/
#include "AutoTypeMac.h"
#include "gui/macutils/MacUtils.h"
#include <ApplicationServices/ApplicationServices.h>
@ -25,8 +26,7 @@
#define INVALID_KEYCODE 0xFFFF
AutoTypePlatformMac::AutoTypePlatformMac()
: m_appkit(new AppKit())
, m_hotkeyRef(nullptr)
: m_hotkeyRef(nullptr)
, m_hotkeyId({ 'kpx2', HOTKEY_ID })
{
EventTypeSpec eventSpec;
@ -79,7 +79,7 @@ QStringList AutoTypePlatformMac::windowTitles()
//
WId AutoTypePlatformMac::activeWindow()
{
return m_appkit->activeProcessId();
return macUtils()->activeWindow();
}
//
@ -159,7 +159,7 @@ AutoTypeExecutor* AutoTypePlatformMac::createExecutor()
//
bool AutoTypePlatformMac::raiseWindow(WId pid)
{
return m_appkit->activateProcess(pid);
return macUtils()->raiseWindow(pid);
}
//
@ -167,7 +167,7 @@ bool AutoTypePlatformMac::raiseWindow(WId pid)
//
bool AutoTypePlatformMac::raiseLastActiveWindow()
{
return m_appkit->activateProcess(m_appkit->lastActiveProcessId());
return macUtils()->raiseLastActiveWindow();
}
//
@ -175,7 +175,7 @@ bool AutoTypePlatformMac::raiseLastActiveWindow()
//
bool AutoTypePlatformMac::raiseOwnWindow()
{
return m_appkit->activateProcess(m_appkit->ownProcessId());
return macUtils()->raiseOwnWindow();
}
//

View File

@ -23,7 +23,6 @@
#include <QtPlugin>
#include <memory>
#include "AppKit.h"
#include "autotype/AutoTypePlatformPlugin.h"
#include "autotype/AutoTypeAction.h"
@ -55,7 +54,6 @@ signals:
void globalShortcutTriggered();
private:
std::unique_ptr<AppKit> m_appkit;
EventHotKeyRef m_hotkeyRef;
EventHotKeyID m_hotkeyId;

View File

@ -1,6 +1,8 @@
set(autotype_mac_SOURCES AutoTypeMac.cpp)
set(autotype_mac_mm_SOURCES AppKitImpl.mm)
set(autotype_mac_mm_SOURCES
${CMAKE_SOURCE_DIR}/src/gui/macutils/AppKitImpl.mm
${CMAKE_SOURCE_DIR}/src/gui/macutils/MacUtils.cpp)
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES} ${autotype_mac_mm_SOURCES})
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")

View File

@ -33,7 +33,12 @@
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/PasswordGenerator.h"
#include "core/Tools.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
#ifdef Q_OS_MACOS
#include "gui/macutils/MacUtils.h"
#endif
const char BrowserService::KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
const char BrowserService::KEEPASSXCBROWSER_OLD_NAME[] = "keepassxc-browser Settings";
@ -49,6 +54,7 @@ BrowserService::BrowserService(DatabaseTabWidget* parent)
: m_dbTabWidget(parent)
, m_dialogActive(false)
, m_bringToFrontRequested(false)
, m_wasMinimized(false)
, m_keepassBrowserUUID(QUuid::fromRfc4122(QByteArray::fromHex("de887cc3036343b8974b5911b8816224")))
{
// Don't connect the signals when used from DatabaseSettingsWidgetBrowser (parent is nullptr)
@ -89,8 +95,9 @@ bool BrowserService::openDatabase(bool triggerUnlock)
}
if (triggerUnlock) {
getMainWindow()->bringToFront();
m_bringToFrontRequested = true;
m_wasMinimized = getMainWindow()->isMinimized();
raiseWindow(true);
}
return false;
@ -157,7 +164,7 @@ QString BrowserService::storeKey(const QString& key)
}
bool contains;
QMessageBox::StandardButton dialogResult = QMessageBox::No;
MessageBox::Button dialogResult = MessageBox::Cancel;
do {
QInputDialog keyDialog;
@ -167,6 +174,7 @@ QString BrowserService::storeKey(const QString& key)
"give it a unique name to identify and accept it."));
keyDialog.setOkButtonText(tr("Save and allow access"));
keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
raiseWindow();
keyDialog.show();
keyDialog.activateWindow();
keyDialog.raise();
@ -175,20 +183,23 @@ QString BrowserService::storeKey(const QString& key)
id = keyDialog.textValue();
if (ok != QDialog::Accepted || id.isEmpty()) {
hideWindow();
return {};
}
contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
if (contains) {
dialogResult = QMessageBox::warning(nullptr,
tr("KeePassXC: Overwrite existing key?"),
tr("A shared encryption key with the name \"%1\" "
"already exists.\nDo you want to overwrite it?")
.arg(id),
QMessageBox::Yes | QMessageBox::No);
dialogResult = MessageBox::warning(nullptr,
tr("KeePassXC: Overwrite existing key?"),
tr("A shared encryption key with the name \"%1\" "
"already exists.\nDo you want to overwrite it?")
.arg(id),
MessageBox::Overwrite | MessageBox::Cancel,
MessageBox::Cancel);
}
} while (contains && dialogResult == QMessageBox::No);
} while (contains && dialogResult == MessageBox::Cancel);
hideWindow();
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key);
return id;
}
@ -367,26 +378,25 @@ void BrowserService::updateEntry(const QString& id,
if (username.compare(login, Qt::CaseSensitive) != 0
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
int dialogResult = QMessageBox::No;
MessageBox::Button dialogResult = MessageBox::No;
if (!browserSettings()->alwaysAllowUpdate()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("KeePassXC: Update Entry"));
msgBox.setText(tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host(), username));
msgBox.setStandardButtons(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setWindowFlags(Qt::WindowStaysOnTopHint);
msgBox.activateWindow();
msgBox.raise();
dialogResult = msgBox.exec();
raiseWindow();
dialogResult = MessageBox::question(nullptr,
tr("KeePassXC: Update Entry"),
tr("Do you want to update the information in %1 - %2?")
.arg(QUrl(url).host(), username),
MessageBox::Save | MessageBox::Cancel,
MessageBox::Cancel, MessageBox::Raise);
}
if (browserSettings()->alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
if (browserSettings()->alwaysAllowUpdate() || dialogResult == MessageBox::Save) {
entry->beginUpdate();
entry->setUsername(login);
entry->setPassword(password);
entry->endUpdate();
}
hideWindow();
}
}
@ -497,24 +507,24 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> curr
progress.reset();
if (counter > 0) {
QMessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully converted attributes from %1 entry(s).\n"
"Moved %2 keys to custom data.",
"")
.arg(counter)
.arg(keyCounter),
QMessageBox::Ok);
MessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully converted attributes from %1 entry(s).\n"
"Moved %2 keys to custom data.",
"")
.arg(counter)
.arg(keyCounter),
MessageBox::Ok);
} else if (counter == 0 && keyCounter > 0) {
QMessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully moved %n keys to custom data.", "", keyCounter),
QMessageBox::Ok);
MessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully moved %n keys to custom data.", "", keyCounter),
MessageBox::Ok);
} else {
QMessageBox::information(nullptr,
tr("KeePassXC: No entry with KeePassHTTP attributes found!"),
tr("The active database does not contain an entry with KeePassHTTP attributes."),
QMessageBox::Ok);
MessageBox::information(nullptr,
tr("KeePassXC: No entry with KeePassHTTP attributes found!"),
tr("The active database does not contain an entry with KeePassHTTP attributes."),
MessageBox::Ok);
}
// Rename password groupName
@ -593,6 +603,11 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
accessControlDialog.setUrl(url);
accessControlDialog.setItems(pwEntriesToConfirm);
raiseWindow();
accessControlDialog.show();
accessControlDialog.activateWindow();
accessControlDialog.raise();
int res = accessControlDialog.exec();
if (accessControlDialog.remember()) {
for (Entry* entry : pwEntriesToConfirm) {
@ -616,6 +631,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
}
m_dialogActive = false;
hideWindow();
if (res == QDialog::Accepted) {
return true;
}
@ -901,13 +917,40 @@ bool BrowserService::checkLegacySettings()
return false;
}
auto dialogResult = QMessageBox::warning(nullptr,
tr("KeePassXC: Legacy browser integration settings detected"),
tr("Legacy browser integration settings have been detected.\n"
"Do you want to upgrade the settings to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
QMessageBox::Yes | QMessageBox::No);
return dialogResult == QMessageBox::Yes;
auto dialogResult = MessageBox::warning(nullptr,
tr("KeePassXC: Legacy browser integration settings detected"),
tr("Legacy browser integration settings have been detected.\n"
"Do you want to upgrade the settings to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
MessageBox::Yes | MessageBox::No);
return dialogResult == MessageBox::Yes;
}
void BrowserService::hideWindow() const
{
if (m_wasMinimized) {
getMainWindow()->showMinimized();
} else {
#ifdef Q_OS_MACOS
macUtils()->raiseLastActiveWindow();
#else
getMainWindow()->lower();
#endif
}
}
void BrowserService::raiseWindow(const bool force)
{
m_wasMinimized = getMainWindow()->isMinimized();
#ifdef Q_OS_MACOS
macUtils()->raiseOwnWindow();
Tools::wait(500);
#else
if (force) {
getMainWindow()->bringToFront();
}
#endif
}
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
@ -921,7 +964,7 @@ void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
{
if (dbWidget) {
if (m_bringToFrontRequested) {
getMainWindow()->lower();
hideWindow();
m_bringToFrontRequested = false;
}
emit databaseUnlocked();

View File

@ -117,11 +117,14 @@ private:
bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const;
bool checkLegacySettings();
void hideWindow() const;
void raiseWindow(const bool force = false);
private:
DatabaseTabWidget* const m_dbTabWidget;
bool m_dialogActive;
bool m_bringToFrontRequested;
bool m_wasMinimized;
QUuid m_keepassBrowserUUID;
};

View File

@ -16,6 +16,7 @@
set(cli_SOURCES
Add.cpp
Clip.cpp
Create.cpp
Command.cpp
Diceware.cpp
Edit.cpp
@ -46,6 +47,44 @@ install(TARGETS keepassxc-cli
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION ${CLI_INSTALL_DIR} COMPONENT Runtime)
if(APPLE AND WITH_APP_BUNDLE)
add_custom_command(TARGET keepassxc-cli
POST_BUILD
COMMAND ${CMAKE_INSTALL_NAME_TOOL}
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore
"@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore"
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui
"@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui"
-change /usr/local/opt/qt/lib/QtMacExtras.framework/Versions/5/QtMacExtras
"@executable_path/../Frameworks/QtMacExtras.framework/Versions/5/QtMacExtras"
-change /usr/local/opt/qt/lib/QtConcurrent.framework/Versions/5/QtConcurrent
"@executable_path/../Frameworks/QtConcurrent.framework/Versions/5/QtConcurrent"
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore
"@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore"
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork
"@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork"
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets
"@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets"
-change /usr/local/opt/qt/lib/QtSvg.framework/Versions/5/QtSvg
"@executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg"
-change /usr/local/opt/libgcrypt/lib/libgcrypt.20.dylib
"@executable_path/../Frameworks/libgcrypt.20.dylib"
-change /usr/local/opt/argon2/lib/libargon2.1.dylib
"@executable_path/../Frameworks/libargon2.1.dylib"
-change /usr/local/opt/libgpg-error/lib/libgpg-error.0.dylib
"@executable_path/../Frameworks/libgpg-error.0.dylib"
-change /usr/local/opt/libsodium/lib/libsodium.23.dylib
"@executable_path/../Frameworks/libsodium.23.dylib"
-change /usr/local/opt/qrencode/lib/libqrencode.4.dylib
"@executable_path/../Frameworks/libqrencode.4.dylib"
-change /usr/local/opt/libyubikey/lib/libyubikey.0.dylib
"@executable_path/../Frameworks/libyubikey.0.dylib"
-change /usr/local/opt/ykpers/lib/libykpers-1.1.dylib
"@executable_path/../Frameworks/libykpers-1.1.dylib"
keepassxc-cli
COMMENT "Changing linking of keepassxc-cli")
endif()
if(APPLE OR UNIX)
install(FILES keepassxc-cli.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
execute_process(COMMAND mandb -q)

View File

@ -24,6 +24,7 @@
#include "Add.h"
#include "Clip.h"
#include "Create.h"
#include "Diceware.h"
#include "Edit.h"
#include "Estimate.h"
@ -69,6 +70,7 @@ void populateCommands()
if (commands.isEmpty()) {
commands.insert(QString("add"), new Add());
commands.insert(QString("clip"), new Clip());
commands.insert(QString("create"), new Create());
commands.insert(QString("diceware"), new Diceware());
commands.insert(QString("edit"), new Edit());
commands.insert(QString("estimate"), new Estimate());

175
src/cli/Create.cpp Normal file
View File

@ -0,0 +1,175 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include <QCommandLineParser>
#include <QFileInfo>
#include <QString>
#include <QTextStream>
#include "Create.h"
#include "Utils.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
#include "keys/Key.h"
Create::Create()
{
name = QString("create");
description = QObject::tr("Create a new database.");
}
Create::~Create()
{
}
/**
* Create a database file using the command line. A key file and/or
* password can be specified to encrypt the password. If none is
* specified the function will fail.
*
* If a key file is specified but it can't be loaded, the function will
* fail.
*
* If the database is being saved in a non existant directory, the
* function will fail.
*
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure
*/
int Create::execute(const QStringList& arguments)
{
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::KeyFileOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() < 1) {
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create");
return EXIT_FAILURE;
}
QString databaseFilename = args.at(0);
if (QFileInfo::exists(databaseFilename)) {
err << QObject::tr("File %1 already exists.").arg(databaseFilename) << endl;
return EXIT_FAILURE;
}
auto key = QSharedPointer<CompositeKey>::create();
auto password = getPasswordFromStdin();
if (!password.isNull()) {
key->addKey(password);
}
QSharedPointer<FileKey> fileKey;
if(parser.isSet(Command::KeyFileOption)) {
if (!loadFileKey(parser.value(Command::KeyFileOption), fileKey)) {
err << QObject::tr("Loading the key file failed") << endl;
return EXIT_FAILURE;
}
}
if (!fileKey.isNull()) {
key->addKey(fileKey);
}
if (key->isEmpty()) {
err << QObject::tr("No key is set. Aborting database creation.") << endl;
return EXIT_FAILURE;
}
Database db;
db.setKey(key);
QString errorMessage;
if (!db.save(databaseFilename, &errorMessage, true, false)) {
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
out << QObject::tr("Successfully created new database.") << endl;
return EXIT_SUCCESS;
}
/**
* Read optional password from stdin.
*
* @return Pointer to the PasswordKey or null if passwordkey is skipped
* by user
*/
QSharedPointer<PasswordKey> Create::getPasswordFromStdin()
{
QSharedPointer<PasswordKey> passwordKey;
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
out << QObject::tr("Insert password to encrypt database (Press enter to leave blank): ");
out.flush();
QString password = Utils::getPassword();
if (!password.isEmpty()) {
passwordKey = QSharedPointer<PasswordKey>(new PasswordKey(password));
}
return passwordKey;
}
/**
* Load a key file from disk. When the path specified does not exist a
* new file will be generated. No folders will be generated so the parent
* folder of the specified file nees to exist
*
* If the key file cannot be loaded or created the function will fail.
*
* @param path Path to the key file to be loaded
* @param fileKey Resulting fileKey
* @return true if the key file was loaded succesfully
*/
bool Create::loadFileKey(QString path, QSharedPointer<FileKey>& fileKey)
{
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
QString error;
fileKey = QSharedPointer<FileKey>(new FileKey());
if (!QFileInfo::exists(path)) {
fileKey->create(path, &error);
if (!error.isEmpty()) {
err << QObject::tr("Creating KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
}
if (!fileKey->load(path, &error)) {
err << QObject::tr("Loading KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
return true;
}

39
src/cli/Create.h Normal file
View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_CREATE_H
#define KEEPASSXC_CREATE_H
#include "Command.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
class Create : public Command
{
public:
Create();
~Create();
int execute(const QStringList& arguments);
private:
QSharedPointer<PasswordKey> getPasswordFromStdin();
QSharedPointer<FileKey> getFileKeyFromStdin();
bool loadFileKey(QString path, QSharedPointer<FileKey>& fileKey);
};
#endif // KEEPASSXC_CREATE_H

View File

@ -19,6 +19,9 @@ Adds a new entry to a database. A password can be generated (\fI-g\fP option), o
.IP "clip [options] <database> <entry> [timeout]"
Copies the password or the current TOTP (\fI-t\fP option) of a database entry to the clipboard. If multiple entries with the same name exist in different groups, only the password for the first one is going to be copied. For copying the password of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name. Optionally, a timeout in seconds can be specified to automatically clear the clipboard.
.IP "create [options] <database>"
Creates a new database with a key file and/or password. The key file will be created if the file that is referred to does not exist. If both the key file and password are empty, no database will be created.
.IP "diceware [options]"
Generate a random diceware passphrase.

View File

@ -42,9 +42,7 @@ int main(int argc, char** argv)
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationVersion(KEEPASSXC_VERSION);
#ifdef QT_NO_DEBUG
Bootstrap::bootstrapApplication();
#endif
Bootstrap::bootstrap();
TextStream out(stdout);
QStringList arguments;

View File

@ -16,12 +16,30 @@
*/
#include "Bootstrap.h"
#include "config-keepassx.h"
#include "core/Config.h"
#include "core/Translator.h"
#include "gui/MessageBox.h"
#ifdef Q_OS_WIN
#include <aclapi.h> // for createWindowsDACL()
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
#undef MessageBox
#endif
#if defined(HAVE_RLIMIT_CORE)
#include <sys/resource.h>
#endif
#if defined(HAVE_PR_SET_DUMPABLE)
#include <sys/prctl.h>
#endif
#ifdef HAVE_PT_DENY_ATTACH
// clang-format off
#include <sys/types.h>
#include <sys/ptrace.h>
// clang-format on
#endif
namespace Bootstrap
@ -44,11 +62,10 @@ namespace Bootstrap
}
/**
* Perform early application bootstrapping such as setting up search paths,
* configuration OS security properties, and loading translators.
* A QApplication object has to be instantiated before calling this function.
* Perform early application bootstrapping that does not rely on a QApplication
* being present.
*/
void bootstrapApplication()
void bootstrap()
{
#ifdef QT_NO_DEBUG
disableCoreDumps();
@ -56,6 +73,17 @@ namespace Bootstrap
setupSearchPaths();
applyEarlyQNetworkAccessManagerWorkaround();
Translator::installTranslators();
}
/**
* Perform early application bootstrapping such as setting up search paths,
* configuration OS security properties, and loading translators.
* A QApplication object has to be instantiated before calling this function.
*/
void bootstrapApplication()
{
bootstrap();
MessageBox::initializeButtonDefs();
#ifdef Q_OS_MACOS
// Don't show menu icons on OSX
@ -137,6 +165,8 @@ namespace Bootstrap
HANDLE hToken = nullptr;
PTOKEN_USER pTokenUser = nullptr;
DWORD cbBufferSize = 0;
PSID pLocalSystemSid = nullptr;
DWORD pLocalSystemSidSize = SECURITY_MAX_SID_SIZE;
// Access control list
PACL pACL = nullptr;
@ -163,8 +193,19 @@ namespace Bootstrap
goto Cleanup;
}
// Retrieve LocalSystem account SID
pLocalSystemSid = static_cast<PSID>(HeapAlloc(GetProcessHeap(), 0, pLocalSystemSidSize));
if (pLocalSystemSid == nullptr) {
goto Cleanup;
}
if (!CreateWellKnownSid(WinLocalSystemSid, nullptr, pLocalSystemSid, &pLocalSystemSidSize)) {
goto Cleanup;
}
// Calculate the amount of memory that must be allocated for the DACL
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid);
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid)
+ sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pLocalSystemSid);
// Create and initialize an ACL
pACL = static_cast<PACL>(HeapAlloc(GetProcessHeap(), 0, cbACL));
@ -186,6 +227,18 @@ namespace Bootstrap
goto Cleanup;
}
#ifdef WITH_XC_SSHAGENT
// OpenSSH for Windows ssh-agent service is running as LocalSystem
if (!AddAccessAllowedAce(
pACL,
ACL_REVISION,
PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE, // just enough for ssh-agent
pLocalSystemSid // known LocalSystem sid
)) {
goto Cleanup;
}
#endif
// Set discretionary access control list
bSuccess = ERROR_SUCCESS
== SetSecurityInfo(GetCurrentProcess(), // object handle
@ -202,6 +255,9 @@ namespace Bootstrap
if (pACL != nullptr) {
HeapFree(GetProcessHeap(), 0, pACL);
}
if (pLocalSystemSid != nullptr) {
HeapFree(GetProcessHeap(), 0, pLocalSystemSid);
}
if (pTokenUser != nullptr) {
HeapFree(GetProcessHeap(), 0, pTokenUser);
}

View File

@ -22,6 +22,7 @@
namespace Bootstrap
{
void bootstrap();
void bootstrapApplication();
void restoreMainWindowState(MainWindow& mainWindow);
void disableCoreDumps();

View File

@ -24,6 +24,7 @@
#include "core/DatabaseIcons.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "totp/totp.h"
#include <QDir>
@ -100,6 +101,32 @@ void Entry::setUpdateTimeinfo(bool value)
m_updateTimeinfo = value;
}
QString Entry::buildReference(const QUuid& uuid, const QString& field)
{
Q_ASSERT(EntryAttributes::DefaultAttributes.count(field) > 0);
QString uuidStr = Tools::uuidToHex(uuid).toUpper();
QString shortField;
if (field == EntryAttributes::TitleKey) {
shortField = "T";
} else if (field == EntryAttributes::UserNameKey) {
shortField = "U";
} else if (field == EntryAttributes::PasswordKey) {
shortField = "P";
} else if (field == EntryAttributes::URLKey) {
shortField = "A";
} else if (field == EntryAttributes::NotesKey) {
shortField = "N";
}
if (shortField.isEmpty()) {
return {};
}
return QString("{REF:%1@I:%2}").arg(shortField, uuidStr);
}
EntryReferenceType Entry::referenceType(const QString& referenceStr)
{
const QString referenceLowerStr = referenceStr.toLower();
@ -130,7 +157,7 @@ const QUuid& Entry::uuid() const
const QString Entry::uuidToHex() const
{
return QString::fromLatin1(m_uuid.toRfc4122().toHex());
return Tools::uuidToHex(m_uuid);
}
QImage Entry::icon() const
@ -304,11 +331,25 @@ QString Entry::notes() const
return m_attributes->value(EntryAttributes::NotesKey);
}
QString Entry::attribute(const QString& key) const
{
return m_attributes->value(key);
}
bool Entry::isExpired() const
{
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTimeUtc();
}
bool Entry::isAttributeReferenceOf(const QString& key, const QUuid& uuid) const
{
if (!m_attributes->isReference(key)) {
return false;
}
return m_attributes->value(key).contains(Tools::uuidToHex(uuid), Qt::CaseInsensitive);
}
bool Entry::hasReferences() const
{
const QList<QString> keyList = EntryAttributes::DefaultAttributes;
@ -320,6 +361,26 @@ bool Entry::hasReferences() const
return false;
}
bool Entry::hasReferencesTo(const QUuid& uuid) const
{
const QList<QString> keyList = EntryAttributes::DefaultAttributes;
for (const QString& key : keyList) {
if (isAttributeReferenceOf(key, uuid)) {
return true;
}
}
return false;
}
void Entry::replaceReferencesWithValues(const Entry* other)
{
for (const QString& key : EntryAttributes::DefaultAttributes) {
if (isAttributeReferenceOf(key, other->uuid())) {
setDefaultAttribute(key, other->attribute(key));
}
}
}
EntryAttributes* Entry::attributes()
{
return m_attributes;
@ -496,6 +557,17 @@ void Entry::setNotes(const QString& notes)
m_attributes->set(EntryAttributes::NotesKey, notes, m_attributes->isProtected(EntryAttributes::NotesKey));
}
void Entry::setDefaultAttribute(const QString& attribute, const QString& value)
{
Q_ASSERT(EntryAttributes::isDefaultAttribute(attribute));
if (!EntryAttributes::isDefaultAttribute(attribute)) {
return;
}
m_attributes->set(attribute, value, m_attributes->isProtected(attribute));
}
void Entry::setExpires(const bool& value)
{
if (m_data.timeInfo.expires() != value) {
@ -654,16 +726,17 @@ Entry* Entry::clone(CloneFlags flags) const
entry->m_attachments->copyDataFrom(m_attachments);
if (flags & CloneUserAsRef) {
// Build the username reference
QString username = "{REF:U@I:" + uuidToHex() + "}";
entry->m_attributes->set(
EntryAttributes::UserNameKey, username.toUpper(), m_attributes->isProtected(EntryAttributes::UserNameKey));
EntryAttributes::UserNameKey,
buildReference(uuid(), EntryAttributes::UserNameKey),
m_attributes->isProtected(EntryAttributes::UserNameKey));
}
if (flags & ClonePassAsRef) {
QString password = "{REF:P@I:" + uuidToHex() + "}";
entry->m_attributes->set(
EntryAttributes::PasswordKey, password.toUpper(), m_attributes->isProtected(EntryAttributes::PasswordKey));
EntryAttributes::PasswordKey,
buildReference(uuid(), EntryAttributes::PasswordKey),
m_attributes->isProtected(EntryAttributes::PasswordKey));
}
entry->m_autoTypeAssociations->copyDataFrom(m_autoTypeAssociations);

View File

@ -105,12 +105,16 @@ public:
QString username() const;
QString password() const;
QString notes() const;
QString attribute(const QString& key) const;
QString totp() const;
QSharedPointer<Totp::Settings> totpSettings() const;
bool hasTotp() const;
bool isExpired() const;
bool isAttributeReferenceOf(const QString& key, const QUuid& uuid) const;
void replaceReferencesWithValues(const Entry* other);
bool hasReferences() const;
bool hasReferencesTo(const QUuid& uuid) const;
EntryAttributes* attributes();
const EntryAttributes* attributes() const;
EntryAttachments* attachments();
@ -139,6 +143,7 @@ public:
void setUsername(const QString& username);
void setPassword(const QString& password);
void setNotes(const QString& notes);
void setDefaultAttribute(const QString& attribute, const QString& value);
void setExpires(const bool& value);
void setExpiryTime(const QDateTime& dateTime);
void setTotp(QSharedPointer<Totp::Settings> settings);
@ -238,6 +243,7 @@ private:
QString resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
QString referenceFieldValue(EntryReferenceType referenceType) const;
static QString buildReference(const QUuid& uuid, const QString& field);
static EntryReferenceType referenceType(const QString& referenceStr);
template <class T> bool set(T& property, const T& value);

View File

@ -23,6 +23,9 @@
#include "core/DatabaseIcons.h"
#include "core/Global.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include <QtConcurrent>
const int Group::DefaultIconNumber = 48;
const int Group::RecycleBinIconNumber = 43;
@ -119,7 +122,7 @@ const QUuid& Group::uuid() const
const QString Group::uuidToHex() const
{
return QString::fromLatin1(m_uuid.toRfc4122().toHex());
return Tools::uuidToHex(m_uuid);
}
QString Group::name() const
@ -548,6 +551,12 @@ QList<Entry*> Group::entriesRecursive(bool includeHistoryItems) const
return entryList;
}
QList<Entry*> Group::referencesRecursive(const Entry* entry) const
{
auto entries = entriesRecursive();
return QtConcurrent::blockingFiltered(entries, [entry](const Entry* e) { return e->hasReferencesTo(entry->uuid()); });
}
Entry* Group::findEntryByUuid(const QUuid& uuid) const
{
if (uuid.isNull()) {

View File

@ -151,6 +151,7 @@ public:
QList<Entry*> entries();
const QList<Entry*>& entries() const;
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group = nullptr);
QList<Entry*> referencesRecursive(const Entry* entry) const;
QList<Entry*> entriesRecursive(bool includeHistoryItems = false) const;
QList<const Group*> groupsRecursive(bool includeSelf) const;
QList<Group*> groupsRecursive(bool includeSelf);

View File

@ -28,6 +28,7 @@
#include <QLocale>
#include <QRegularExpression>
#include <QStringList>
#include <QUuid>
#include <cctype>
#ifdef Q_OS_WIN
@ -38,23 +39,6 @@
#include <time.h> // for nanosleep()
#endif
#include "config-keepassx.h"
#if defined(HAVE_RLIMIT_CORE)
#include <sys/resource.h>
#endif
#if defined(HAVE_PR_SET_DUMPABLE)
#include <sys/prctl.h>
#endif
#ifdef HAVE_PT_DENY_ATTACH
// clang-format off
#include <sys/types.h>
#include <sys/ptrace.h>
// clang-format on
#endif
namespace Tools
{
QString humanReadableFileSize(qint64 bytes, quint32 precision)
@ -197,34 +181,37 @@ namespace Tools
}
}
// Escape common regex symbols except for *, ?, and |
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
// Escape common regex symbols except for *, ?, and |
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive)
{
QString pattern = string;
QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive)
{
QString pattern = string;
// Wildcard support (*, ?, |)
if (useWildcards) {
pattern.replace(regexEscape, "\\\\1");
pattern.replace("*", ".*");
pattern.replace("?", ".");
// Wildcard support (*, ?, |)
if (useWildcards) {
pattern.replace(regexEscape, "\\\\1");
pattern.replace("*", ".*");
pattern.replace("?", ".");
}
// Exact modifier
if (exactMatch) {
pattern = "^" + pattern + "$";
}
auto regex = QRegularExpression(pattern);
if (!caseSensitive) {
regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
}
return regex;
}
// Exact modifier
if (exactMatch) {
pattern = "^" + pattern + "$";
QString uuidToHex(const QUuid& uuid) {
return QString::fromLatin1(uuid.toRfc4122().toHex());
}
auto regex = QRegularExpression(pattern);
if (!caseSensitive) {
regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
}
return regex;
}
Buffer::Buffer()
: raw(nullptr)
, size(0)
@ -250,5 +237,4 @@ QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool
return QByteArray(reinterpret_cast<char*>(raw), size );
}
} // namespace Tools

View File

@ -23,6 +23,7 @@
#include <QObject>
#include <QString>
#include <QUuid>
#include <algorithm>
@ -39,7 +40,8 @@ namespace Tools
bool isBase64(const QByteArray& ba);
void sleep(int ms);
void wait(int ms);
QRegularExpression convertToRegex(const QString& string, bool useWildcards = false,
QString uuidToHex(const QUuid& uuid);
QRegularExpression convertToRegex(const QString& string, bool useWildcards = false,
bool exactMatch = false, bool caseSensitive = false);
template <typename RandomAccessIterator, typename T>

View File

@ -47,7 +47,8 @@ void Translator::installTranslators()
#ifdef QT_DEBUG
QString("%1/share/translations").arg(KEEPASSX_BINARY_DIR),
#endif
filePath()->dataPath("translations")};
filePath()->dataPath("translations")
};
bool translationsLoaded = false;
for (const QString& path : paths) {

View File

@ -25,7 +25,11 @@ DatabaseOpenDialog::DatabaseOpenDialog(QWidget* parent)
, m_view(new DatabaseOpenWidget(this))
{
setWindowTitle(tr("Unlock Database - KeePassXC"));
#ifdef Q_OS_MACOS
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
#else
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint | Qt::ForeignWindow);
#endif
connect(m_view, SIGNAL(dialogFinished(bool)), this, SLOT(complete(bool)));
}

View File

@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_AUTOTYPEUNLOCKDIALOG_H
#define KEEPASSX_AUTOTYPEUNLOCKDIALOG_H
#ifndef KEEPASSX_UNLOCKDATABASEDIALOG_H
#define KEEPASSX_UNLOCKDATABASEDIALOG_H
#include "core/Global.h"
@ -37,7 +37,8 @@ public:
{
None,
AutoType,
Merge
Merge,
Browser
};
explicit DatabaseOpenDialog(QWidget* parent = nullptr);
@ -61,4 +62,4 @@ private:
Intent m_intent = Intent::None;
};
#endif // KEEPASSX_AUTOTYPEUNLOCKDIALOG_H
#endif // KEEPASSX_UNLOCKDATABASEDIALOG_H

View File

@ -39,6 +39,9 @@
#include "gui/DatabaseOpenDialog.h"
#include "gui/entry/EntryView.h"
#include "gui/group/GroupView.h"
#ifdef Q_OS_MACOS
#include "gui/macutils/MacUtils.h"
#endif
#include "gui/wizard/NewDatabaseWizard.h"
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
@ -98,8 +101,8 @@ QSharedPointer<Database> DatabaseTabWidget::execNewDatabaseWizard()
tr("Database creation error"),
tr("The created database has no key or KDF, refusing to save it.\n"
"This is definitely a bug, please report it to the developers."),
QMessageBox::Ok,
QMessageBox::Ok);
MessageBox::Ok,
MessageBox::Ok);
return {};
}
@ -544,8 +547,8 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget, Databas
m_databaseOpenDialog->setFilePath(filePath);
#ifdef Q_OS_MACOS
if (intent == DatabaseOpenDialog::Intent::AutoType) {
autoType()->raiseWindow();
if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
macUtils()->raiseOwnWindow();
Tools::wait(500);
}
#endif

View File

@ -131,7 +131,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);"
"background-color: rgb(255, 253, 160);"
"border: 2px solid rgb(190, 190, 190);"
"border-radius: 2px;");
"border-radius: 4px;");
m_searchingLabel->setVisible(false);
m_previewView->hide();
@ -425,61 +425,117 @@ void DatabaseWidget::setupTotp()
setupTotpDialog->open();
}
void DatabaseWidget::deleteEntries()
void DatabaseWidget::deleteSelectedEntries()
{
const QModelIndexList selected = m_entryView->selectionModel()->selectedRows();
Q_ASSERT(!selected.isEmpty());
if (selected.isEmpty()) {
return;
}
// get all entry pointers as the indexes change when removing multiple entries
// Resolve entries from the selection model
QList<Entry*> selectedEntries;
for (const QModelIndex& index : selected) {
selectedEntries.append(m_entryView->entryFromIndex(index));
}
// Confirm entry removal before moving forward
auto* recycleBin = m_db->metadata()->recycleBin();
bool inRecycleBin = recycleBin && recycleBin->findEntryByUuid(selectedEntries.first()->uuid());
if (inRecycleBin || !m_db->metadata()->recycleBinEnabled()) {
QString prompt;
if (selected.size() == 1) {
prompt = tr("Do you really want to delete the entry \"%1\" for good?")
.arg(selectedEntries.first()->title().toHtmlEscaped());
} else {
prompt = tr("Do you really want to delete %n entry(s) for good?", "", selected.size());
bool permanent = (recycleBin && recycleBin->findEntryByUuid(selectedEntries.first()->uuid()))
|| !m_db->metadata()->recycleBinEnabled();
if (!confirmDeleteEntries(selectedEntries, permanent)) {
return;
}
// Find references to selected entries and prompt for direction if necessary
auto it = selectedEntries.begin();
while (it != selectedEntries.end()) {
auto references = m_db->rootGroup()->referencesRecursive(*it);
if (!references.isEmpty()) {
// Ignore references that are selected for deletion
for (auto* entry : selectedEntries) {
references.removeAll(entry);
}
if (!references.isEmpty()) {
// Prompt for reference handling
auto result = MessageBox::question(
this,
tr("Replace references to entry?"),
tr("Entry \"%1\" has %2 reference(s). "
"Do you want to overwrite references with values, skip this entry, or delete anyway?", "",
references.size())
.arg((*it)->title().toHtmlEscaped())
.arg(references.size()),
MessageBox::Overwrite | MessageBox::Skip | MessageBox::Delete,
MessageBox::Overwrite);
if (result == MessageBox::Overwrite) {
for (auto* entry : references) {
entry->replaceReferencesWithValues(*it);
}
} else if (result == MessageBox::Skip) {
it = selectedEntries.erase(it);
continue;
}
}
}
QMessageBox::StandardButton result = MessageBox::question(
this, tr("Delete entry(s)?", "", selected.size()), prompt, QMessageBox::Yes | QMessageBox::No);
it++;
}
if (result == QMessageBox::Yes) {
for (Entry* entry : asConst(selectedEntries)) {
delete entry;
}
refreshSearch();
if (permanent) {
for (auto* entry : asConst(selectedEntries)) {
delete entry;
}
} else {
QString prompt;
if (selected.size() == 1) {
prompt = tr("Do you really want to move entry \"%1\" to the recycle bin?")
.arg(selectedEntries.first()->title().toHtmlEscaped());
} else {
prompt = tr("Do you really want to move %n entry(s) to the recycle bin?", "", selected.size());
}
QMessageBox::StandardButton result = MessageBox::question(
this, tr("Move entry(s) to recycle bin?", "", selected.size()), prompt, QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No) {
return;
}
for (Entry* entry : asConst(selectedEntries)) {
for (auto* entry : asConst(selectedEntries)) {
m_db->recycleEntry(entry);
}
}
refreshSearch();
}
bool DatabaseWidget::confirmDeleteEntries(QList<Entry*> entries, bool permanent)
{
if (entries.isEmpty()) {
return false;
}
if (permanent) {
QString prompt;
if (entries.size() == 1) {
prompt = tr("Do you really want to delete the entry \"%1\" for good?")
.arg(entries.first()->title().toHtmlEscaped());
} else {
prompt = tr("Do you really want to delete %n entry(s) for good?", "", entries.size());
}
auto answer = MessageBox::question(this,
tr("Delete entry(s)?", "", entries.size()),
prompt,
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
return answer == MessageBox::Delete;
} else {
QString prompt;
if (entries.size() == 1) {
prompt = tr("Do you really want to move entry \"%1\" to the recycle bin?")
.arg(entries.first()->title().toHtmlEscaped());
} else {
prompt = tr("Do you really want to move %n entry(s) to the recycle bin?", "", entries.size());
}
auto answer = MessageBox::question(this,
tr("Move entry(s) to recycle bin?", "", entries.size()),
prompt,
MessageBox::Move | MessageBox::Cancel,
MessageBox::Cancel);
return answer == MessageBox::Move;
}
}
void DatabaseWidget::setFocus()
@ -649,16 +705,27 @@ void DatabaseWidget::deleteGroup()
bool isRecycleBin = recycleBin && (currentGroup == recycleBin);
bool isRecycleBinSubgroup = recycleBin && currentGroup->findGroupByUuid(recycleBin->uuid());
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
QMessageBox::StandardButton result = MessageBox::question(
this,
tr("Delete group?"),
tr("Do you really want to delete the group \"%1\" for good?").arg(currentGroup->name().toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
auto result = MessageBox::question(this,
tr("Delete group"),
tr("Do you really want to delete the group \"%1\" for good?")
.arg(currentGroup->name().toHtmlEscaped()),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Delete) {
delete currentGroup;
}
} else {
m_db->recycleGroup(currentGroup);
auto result = MessageBox::question(this,
tr("Move group to recycle bin?"),
tr("Do you really want to move the group "
"\"%1\" to the recycle bin?")
.arg(currentGroup->name().toHtmlEscaped()),
MessageBox::Move | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Move) {
m_db->recycleGroup(currentGroup);
}
}
}
@ -1126,8 +1193,8 @@ bool DatabaseWidget::lock()
if (currentMode() == DatabaseWidget::Mode::EditMode) {
auto result = MessageBox::question(this, tr("Lock Database?"),
tr("You are editing an entry. Discard changes and lock anyway?"),
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
if (result == QMessageBox::Cancel) {
MessageBox::Discard | MessageBox::Cancel, MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return false;
}
}
@ -1145,10 +1212,10 @@ bool DatabaseWidget::lock()
msg = tr("Database was modified.\nSave changes?");
}
auto result = MessageBox::question(this, tr("Save changes?"), msg,
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes && !m_db->save(nullptr, false, false)) {
MessageBox::Save | MessageBox::Discard | MessageBox::Cancel, MessageBox::Save);
if (result == MessageBox::Save && !m_db->save(nullptr, false, false)) {
return false;
} else if (result == QMessageBox::Cancel) {
} else if (result == MessageBox::Cancel) {
return false;
}
}
@ -1202,9 +1269,9 @@ void DatabaseWidget::reloadDatabaseFile()
auto result = MessageBox::question(this,
tr("File has changed"),
tr("The database file has changed. Do you want to load the changes?"),
QMessageBox::Yes | QMessageBox::No);
MessageBox::Yes | MessageBox::No);
if (result == QMessageBox::No) {
if (result == MessageBox::No) {
// Notify everyone the database does not match the file
m_db->markAsModified();
// Rewatch the database file
@ -1221,9 +1288,10 @@ void DatabaseWidget::reloadDatabaseFile()
auto result = MessageBox::question(this,
tr("Merge Request"),
tr("The database file has changed and you have unsaved changes.\nDo you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No);
MessageBox::Merge | MessageBox::Cancel,
MessageBox::Merge);
if (result == QMessageBox::Yes) {
if (result == MessageBox::Merge) {
// Merge the old database into the new one
Merger merger(m_db.data(), db.data());
merger.merge();
@ -1409,14 +1477,14 @@ bool DatabaseWidget::save(int attempt)
if (attempt > 2 && useAtomicSaves) {
// Saving failed 3 times, issue a warning and attempt to resolve
auto choice = MessageBox::question(this,
auto result = MessageBox::question(this,
tr("Disable safe saves?"),
tr("KeePassXC has failed to save the database multiple times. "
"This is likely caused by file sync services holding a lock on "
"the save file.\nDisable safe saves and try again?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (choice == QMessageBox::Yes) {
MessageBox::Disable | MessageBox::Cancel,
MessageBox::Disable);
if (result == MessageBox::Disable) {
config()->set("UseAtomicSaves", false);
return save(attempt + 1);
}
@ -1491,13 +1559,13 @@ void DatabaseWidget::emptyRecycleBin()
return;
}
QMessageBox::StandardButton result =
MessageBox::question(this,
tr("Empty recycle bin?"),
tr("Are you sure you want to permanently delete everything from your recycle bin?"),
QMessageBox::Yes | QMessageBox::No);
auto result = MessageBox::question(this,
tr("Empty recycle bin?"),
tr("Are you sure you want to permanently delete everything from your recycle bin?"),
MessageBox::Empty | MessageBox::Cancel,
MessageBox::Cancel);
if (result == QMessageBox::Yes) {
if (result == MessageBox::Empty) {
m_db->emptyRecycleBin();
refreshSearch();
}

View File

@ -149,7 +149,7 @@ public slots:
void replaceDatabase(QSharedPointer<Database> db);
void createEntry();
void cloneEntry();
void deleteEntries();
void deleteSelectedEntries();
void setFocus();
void copyTitle();
void copyUsername();
@ -223,6 +223,7 @@ private:
void setClipboardTextAndMinimize(const QString& text);
void setIconFromParent();
void processAutoOpen();
bool confirmDeleteEntries(QList<Entry*> entries, bool permanent);
QSharedPointer<Database> m_db;

View File

@ -416,16 +416,18 @@ void EditWidgetIcons::removeCustomIcon()
int iconUseCount = entriesWithSameIcon.size() + groupsWithSameIcon.size();
if (iconUseCount > 0) {
QMessageBox::StandardButton ans =
MessageBox::question(this,
tr("Confirm Delete"),
tr("This icon is used by %n entry(s), and will be replaced "
"by the default icon. Are you sure you want to delete it?",
"",
iconUseCount),
QMessageBox::Yes | QMessageBox::No);
if (ans == QMessageBox::No) {
auto result = MessageBox::question(this,
tr("Confirm Delete"),
tr("This icon is used by %n entry(s), and will be replaced "
"by the default icon. Are you sure you want to delete it?",
"",
iconUseCount),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
// Early out, nothing is changed
return;
} else {

View File

@ -71,13 +71,14 @@ void EditWidgetProperties::setCustomData(CustomData* customData)
void EditWidgetProperties::removeSelectedPluginData()
{
if (QMessageBox::Yes
!= MessageBox::question(this,
tr("Delete plugin data?"),
tr("Do you really want to delete the selected plugin data?\n"
"This may cause the affected plugins to malfunction."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
auto result = MessageBox::question(this,
tr("Delete plugin data?"),
tr("Do you really want to delete the selected plugin data?\n"
"This may cause the affected plugins to malfunction."),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return;
}

View File

@ -28,6 +28,9 @@
</property>
<item>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="labelCreated">
<property name="text">

View File

@ -321,7 +321,7 @@ MainWindow::MainWindow()
m_actionMultiplexer.connect(m_ui->actionEntryNew, SIGNAL(triggered()), SLOT(createEntry()));
m_actionMultiplexer.connect(m_ui->actionEntryClone, SIGNAL(triggered()), SLOT(cloneEntry()));
m_actionMultiplexer.connect(m_ui->actionEntryEdit, SIGNAL(triggered()), SLOT(switchToEntryEdit()));
m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()), SLOT(deleteEntries()));
m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()), SLOT(deleteSelectedEntries()));
m_actionMultiplexer.connect(m_ui->actionEntryTotp, SIGNAL(triggered()), SLOT(showTotp()));
m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()), SLOT(setupTotp()));

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,69 +18,149 @@
#include "MessageBox.h"
QMessageBox::StandardButton MessageBox::m_nextAnswer(QMessageBox::NoButton);
MessageBox::Button MessageBox::m_nextAnswer(MessageBox::NoButton);
QMessageBox::StandardButton MessageBox::critical(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
QMap<QAbstractButton*, MessageBox::Button>
MessageBox::m_addedButtonLookup =
QMap<QAbstractButton*, MessageBox::Button>();
QMap<MessageBox::Button, std::pair<QString, QMessageBox::ButtonRole>>
MessageBox::m_buttonDefs =
QMap<MessageBox::Button, std::pair<QString, QMessageBox::ButtonRole>>();
void MessageBox::initializeButtonDefs()
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::critical(parent, title, text, buttons, defaultButton);
m_buttonDefs =
QMap<Button, std::pair<QString, QMessageBox::ButtonRole>>
{
// Reimplementation of Qt StandardButtons
{Ok, {stdButtonText(QMessageBox::Ok), QMessageBox::ButtonRole::AcceptRole}},
{Open, {stdButtonText(QMessageBox::Open), QMessageBox::ButtonRole::AcceptRole}},
{Save, {stdButtonText(QMessageBox::Save), QMessageBox::ButtonRole::AcceptRole}},
{Cancel, {stdButtonText(QMessageBox::Cancel), QMessageBox::ButtonRole::RejectRole}},
{Close, {stdButtonText(QMessageBox::Close), QMessageBox::ButtonRole::RejectRole}},
{Discard, {stdButtonText(QMessageBox::Discard), QMessageBox::ButtonRole::DestructiveRole}},
{Apply, {stdButtonText(QMessageBox::Apply), QMessageBox::ButtonRole::ApplyRole}},
{Reset, {stdButtonText(QMessageBox::Reset), QMessageBox::ButtonRole::ResetRole}},
{RestoreDefaults, {stdButtonText(QMessageBox::RestoreDefaults), QMessageBox::ButtonRole::ResetRole}},
{Help, {stdButtonText(QMessageBox::Help), QMessageBox::ButtonRole::HelpRole}},
{SaveAll, {stdButtonText(QMessageBox::SaveAll), QMessageBox::ButtonRole::AcceptRole}},
{Yes, {stdButtonText(QMessageBox::Yes), QMessageBox::ButtonRole::YesRole}},
{YesToAll, {stdButtonText(QMessageBox::YesToAll), QMessageBox::ButtonRole::YesRole}},
{No, {stdButtonText(QMessageBox::No), QMessageBox::ButtonRole::NoRole}},
{NoToAll, {stdButtonText(QMessageBox::NoToAll), QMessageBox::ButtonRole::NoRole}},
{Abort, {stdButtonText(QMessageBox::Abort), QMessageBox::ButtonRole::RejectRole}},
{Retry, {stdButtonText(QMessageBox::Retry), QMessageBox::ButtonRole::AcceptRole}},
{Ignore, {stdButtonText(QMessageBox::Ignore), QMessageBox::ButtonRole::AcceptRole}},
// KeePassXC Buttons
{Overwrite, {QMessageBox::tr("Overwrite"), QMessageBox::ButtonRole::AcceptRole}},
{Delete, {QMessageBox::tr("Delete"), QMessageBox::ButtonRole::AcceptRole}},
{Move, {QMessageBox::tr("Move"), QMessageBox::ButtonRole::AcceptRole}},
{Empty, {QMessageBox::tr("Empty"), QMessageBox::ButtonRole::AcceptRole}},
{Remove, {QMessageBox::tr("Remove"), QMessageBox::ButtonRole::AcceptRole}},
{Skip, {QMessageBox::tr("Skip"), QMessageBox::ButtonRole::AcceptRole}},
{Disable, {QMessageBox::tr("Disable"), QMessageBox::ButtonRole::AcceptRole}},
{Merge, {QMessageBox::tr("Merge"), QMessageBox::ButtonRole::AcceptRole}},
};
}
QString MessageBox::stdButtonText(QMessageBox::StandardButton button)
{
QMessageBox buttonHost;
return buttonHost.addButton(button)->text();
}
MessageBox::Button MessageBox::messageBox(QWidget* parent,
QMessageBox::Icon icon,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton,
MessageBox::Action action)
{
if (m_nextAnswer == MessageBox::NoButton) {
QMessageBox msgBox(parent);
msgBox.setIcon(icon);
msgBox.setWindowTitle(title);
msgBox.setText(text);
for (uint64_t b = First; b <= Last; b <<= 1) {
if (b & buttons) {
QString text = m_buttonDefs[static_cast<Button>(b)].first;
QMessageBox::ButtonRole role = m_buttonDefs[static_cast<Button>(b)].second;
auto buttonPtr = msgBox.addButton(text, role);
m_addedButtonLookup.insert(buttonPtr, static_cast<Button>(b));
}
}
if (defaultButton != MessageBox::NoButton) {
QList<QAbstractButton*> defPtrList = m_addedButtonLookup.keys(defaultButton);
if (defPtrList.count() > 0) {
msgBox.setDefaultButton(static_cast<QPushButton*>(defPtrList[0]));
}
}
if (action == MessageBox::Raise) {
msgBox.setWindowFlags(Qt::WindowStaysOnTopHint);
msgBox.activateWindow();
msgBox.raise();
}
msgBox.exec();
Button returnButton = m_addedButtonLookup[msgBox.clickedButton()];
m_addedButtonLookup.clear();
return returnButton;
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
MessageBox::Button returnButton = m_nextAnswer;
m_nextAnswer = MessageBox::NoButton;
return returnButton;
}
}
QMessageBox::StandardButton MessageBox::information(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
MessageBox::Button MessageBox::critical(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton,
MessageBox::Action action)
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::information(parent, title, text, buttons, defaultButton);
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
return returnButton;
}
return messageBox(parent, QMessageBox::Critical, title, text, buttons, defaultButton, action);
}
QMessageBox::StandardButton MessageBox::question(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
MessageBox::Button MessageBox::information(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton,
MessageBox::Action action)
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::question(parent, title, text, buttons, defaultButton);
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
return returnButton;
}
return messageBox(parent, QMessageBox::Information, title, text, buttons, defaultButton, action);
}
QMessageBox::StandardButton MessageBox::warning(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
MessageBox::Button MessageBox::question(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton,
MessageBox::Action action)
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::warning(parent, title, text, buttons, defaultButton);
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
return returnButton;
}
return messageBox(parent, QMessageBox::Question, title, text, buttons, defaultButton, action);
}
void MessageBox::setNextAnswer(QMessageBox::StandardButton button)
MessageBox::Button MessageBox::warning(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton,
MessageBox::Action action)
{
return messageBox(parent, QMessageBox::Warning, title, text, buttons, defaultButton, action);
}
void MessageBox::setNextAnswer(MessageBox::Button button)
{
m_nextAnswer = button;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,35 +20,101 @@
#define KEEPASSX_MESSAGEBOX_H
#include <QMessageBox>
#include <QPushButton>
#include <QMap>
class MessageBox
{
public:
static QMessageBox::StandardButton critical(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton information(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton question(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton warning(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
enum Button : uint64_t {
// Reimplementation of Qt StandardButtons
NoButton = 0,
Ok = 1 << 1,
Open = 1 << 2,
Save = 1 << 3,
Cancel = 1 << 4,
Close = 1 << 5,
Discard = 1 << 6,
Apply = 1 << 7,
Reset = 1 << 8,
RestoreDefaults = 1 << 9,
Help = 1 << 10,
SaveAll = 1 << 11,
Yes = 1 << 12,
YesToAll = 1 << 13,
No = 1 << 14,
NoToAll = 1 << 15,
Abort = 1 << 16,
Retry = 1 << 17,
Ignore = 1 << 18,
static void setNextAnswer(QMessageBox::StandardButton button);
// KeePassXC Buttons
Overwrite = 1 << 19,
Delete = 1 << 20,
Move = 1 << 21,
Empty = 1 << 22,
Remove = 1 << 23,
Skip = 1 << 24,
Disable = 1 << 25,
Merge = 1 << 26,
// Internal loop markers. Update Last when new KeePassXC button is added
First = Ok,
Last = Merge,
};
enum Action {
None = 0,
Raise = 1,
};
typedef uint64_t Buttons;
static void initializeButtonDefs();
static void setNextAnswer(Button button);
static Button critical(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton,
Action action = MessageBox::None);
static Button information(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton,
Action action = MessageBox::None);
static Button question(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton,
Action action = MessageBox::None);
static Button warning(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton,
Action action = MessageBox::None);
private:
static QMessageBox::StandardButton m_nextAnswer;
static Button m_nextAnswer;
static QMap<QAbstractButton*, Button> m_addedButtonLookup;
static QMap<Button, std::pair<QString, QMessageBox::ButtonRole>> m_buttonDefs;
static Button messageBox(QWidget* parent,
QMessageBox::Icon icon,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton,
Action action = MessageBox::None);
static QString stdButtonText(QMessageBox::StandardButton button);
};
#endif // KEEPASSX_MESSAGEBOX_H

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QKeyEvent>
#include "WelcomeWidget.h"
#include "ui_WelcomeWidget.h"
@ -76,3 +77,11 @@ void WelcomeWidget::refreshLastDatabases()
m_ui->recentListWidget->addItem(itm);
}
}
void WelcomeWidget::keyPressEvent(QKeyEvent *event) {
if (m_ui->recentListWidget->hasFocus() && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)) {
openDatabaseFromFile(m_ui->recentListWidget->currentItem());
}
QWidget::keyPressEvent(event);
}

View File

@ -43,6 +43,9 @@ signals:
void importKeePass1Database();
void importCsv();
protected:
void keyPressEvent(QKeyEvent *event) override;
private slots:
void openDatabaseFromFile(QListWidgetItem* item);

View File

@ -278,8 +278,8 @@ void CsvImportWidget::writeDatabase()
MessageBox::warning(this,
tr("Error"),
tr("CSV import: writer has errors:\n%1").arg(writer.errorString()),
QMessageBox::Ok,
QMessageBox::Ok);
MessageBox::Ok,
MessageBox::Ok);
}
emit editFinished(true);
}

View File

@ -91,13 +91,13 @@ bool DatabaseSettingsWidgetBrowser::save()
void DatabaseSettingsWidgetBrowser::removeSelectedKey()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(this,
tr("Delete the selected key?"),
tr("Do you really want to delete the selected key?\n"
"This may prevent connection to the browser plugin."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}
@ -156,13 +156,13 @@ void DatabaseSettingsWidgetBrowser::settingsWarning()
void DatabaseSettingsWidgetBrowser::removeSharedEncryptionKeys()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(this,
tr("Disconnect all browsers"),
tr("Do you really want to disconnect all browsers?\n"
"This may prevent connection to the browser plugin."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}
@ -174,10 +174,10 @@ void DatabaseSettingsWidgetBrowser::removeSharedEncryptionKeys()
}
if (keysToRemove.isEmpty()) {
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: No keys found"),
tr("No shared encryption keys found in KeePassXC settings."),
QMessageBox::Ok);
MessageBox::Ok);
return;
}
@ -186,21 +186,21 @@ void DatabaseSettingsWidgetBrowser::removeSharedEncryptionKeys()
}
const int count = keysToRemove.count();
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: Removed keys from database"),
tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
QMessageBox::Ok);
MessageBox::Ok);
}
void DatabaseSettingsWidgetBrowser::removeStoredPermissions()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(this,
tr("Forget all site-specific settings on entries"),
tr("Do you really want forget all site-specific settings on every entry?\n"
"Permissions to access entries will be revoked."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}
@ -226,28 +226,28 @@ void DatabaseSettingsWidgetBrowser::removeStoredPermissions()
progress.reset();
if (counter > 0) {
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: Removed permissions"),
tr("Successfully removed permissions from %n entry(s).", "", counter),
QMessageBox::Ok);
MessageBox::Ok);
} else {
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: No entry with permissions found!"),
tr("The active database does not contain an entry with permissions."),
QMessageBox::Ok);
MessageBox::Ok);
}
}
void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(
this,
tr("Move KeePassHTTP attributes to custom data"),
tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}

View File

@ -108,7 +108,7 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="groupBox_3">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QTableView" name="customDataTable">

View File

@ -172,8 +172,8 @@ bool DatabaseSettingsWidgetMasterKey::save()
MessageBox::critical(this,
tr("No encryption key added"),
tr("You must add at least one encryption key to secure your database!"),
QMessageBox::Ok,
QMessageBox::Ok);
MessageBox::Ok,
MessageBox::Ok);
return false;
}
@ -183,9 +183,9 @@ bool DatabaseSettingsWidgetMasterKey::save()
tr("WARNING! You have not set a password. Using a database without "
"a password is strongly discouraged!\n\n"
"Are you sure you want to continue without a password?"),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel);
if (answer != QMessageBox::Yes) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel);
if (answer != MessageBox::Yes) {
return false;
}
}
@ -221,7 +221,7 @@ bool DatabaseSettingsWidgetMasterKey::addToCompositeKey(KeyComponentWidget* widg
if (widget->visiblePage() == KeyComponentWidget::Edit) {
QString error = tr("Unknown error");
if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) {
QMessageBox::critical(this, tr("Failed to change master key"), error, QMessageBox::Ok);
MessageBox::critical(this, tr("Failed to change master key"), error, MessageBox::Ok);
return false;
}
} else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) {
@ -238,7 +238,7 @@ bool DatabaseSettingsWidgetMasterKey::addToCompositeKey(KeyComponentWidget* widg
if (widget->visiblePage() == KeyComponentWidget::Edit) {
QString error = tr("Unknown error");
if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) {
QMessageBox::critical(this, tr("Failed to change master key"), error, QMessageBox::Ok);
MessageBox::critical(this, tr("Failed to change master key"), error, MessageBox::Ok);
return false;
}
} else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) {

View File

@ -829,9 +829,9 @@ bool EditEntryWidget::commitEntry()
auto answer = MessageBox::question(this,
tr("Apply generated password?"),
tr("Do you want to apply the generated password to this entry?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (answer == QMessageBox::Yes) {
MessageBox::Yes | MessageBox::No,
MessageBox::Yes);
if (answer == MessageBox::Yes) {
m_mainUi->passwordGenerator->applyPassword();
}
}
@ -955,13 +955,13 @@ void EditEntryWidget::cancel()
auto result = MessageBox::question(this,
QString(),
tr("Entry has unsaved changes"),
QMessageBox::Cancel | QMessageBox::Save | QMessageBox::Discard,
QMessageBox::Cancel);
if (result == QMessageBox::Cancel) {
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
m_mainUi->passwordGenerator->reset();
return;
}
if (result == QMessageBox::Save) {
if (result == MessageBox::Save) {
commitEntry();
m_saved = true;
}
@ -1066,11 +1066,14 @@ void EditEntryWidget::removeCurrentAttribute()
QModelIndex index = m_advancedUi->attributesView->currentIndex();
if (index.isValid()) {
if (MessageBox::question(this,
tr("Confirm Remove"),
tr("Are you sure you want to remove this attribute?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes) {
auto result = MessageBox::question(this,
tr("Confirm Removal"),
tr("Are you sure you want to remove this attribute?"),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Remove) {
m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
setUnsavedChanges(true);
}

View File

@ -165,10 +165,13 @@ void EntryAttachmentsWidget::removeSelectedAttachments()
return;
}
const QString question = tr("Are you sure you want to remove %n attachment(s)?", "", indexes.count());
QMessageBox::StandardButton answer =
MessageBox::question(this, tr("Confirm remove"), question, QMessageBox::Yes | QMessageBox::No);
if (answer == QMessageBox::Yes) {
auto result = MessageBox::question(this,
tr("Confirm remove"),
tr("Are you sure you want to remove %n attachment(s)?", "", indexes.count()),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Remove) {
QStringList keys;
for (const QModelIndex& index : indexes) {
keys.append(m_attachmentsModel->keyByIndex(index));
@ -211,15 +214,24 @@ void EntryAttachmentsWidget::saveSelectedAttachments()
const QString attachmentPath = saveDir.absoluteFilePath(filename);
if (QFileInfo::exists(attachmentPath)) {
const QString question(
MessageBox::Buttons buttons = MessageBox::Overwrite | MessageBox::Cancel;
if (indexes.length() > 1) {
buttons |= MessageBox::Skip;
}
const QString questionText(
tr("Are you sure you want to overwrite the existing file \"%1\" with the attachment?"));
auto answer = MessageBox::question(this,
auto result = MessageBox::question(this,
tr("Confirm overwrite"),
question.arg(filename),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (answer == QMessageBox::No) {
questionText.arg(filename),
buttons,
MessageBox::Cancel);
if (result == MessageBox::Skip) {
continue;
} else if (answer == QMessageBox::Cancel) {
} else if (result == MessageBox::Cancel) {
return;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MacUtils.h"
#include <QApplication>
MacUtils* MacUtils::m_instance = nullptr;
MacUtils::MacUtils(QObject* parent) : QObject(parent)
, m_appkit(new AppKit())
{
}
MacUtils::~MacUtils()
{
}
MacUtils* MacUtils::instance()
{
if (!m_instance) {
m_instance = new MacUtils(qApp);
}
return m_instance;
}
WId MacUtils::activeWindow()
{
return m_appkit->activeProcessId();
}
bool MacUtils::raiseWindow(WId pid)
{
return m_appkit->activateProcess(pid);
}
bool MacUtils::raiseOwnWindow()
{
return m_appkit->activateProcess(m_appkit->ownProcessId());
}
bool MacUtils::raiseLastActiveWindow()
{
return m_appkit->activateProcess(m_appkit->lastActiveProcessId());
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_MACUTILS_H
#define KEEPASSXC_MACUTILS_H
#include <QObject>
#include <QWidget>
#include "AppKit.h"
class MacUtils : public QObject
{
Q_OBJECT
public:
static MacUtils* instance();
static void createTestInstance();
WId activeWindow();
bool raiseWindow(WId pid);
bool raiseLastActiveWindow();
bool raiseOwnWindow();
private:
explicit MacUtils(QObject* parent = nullptr);
~MacUtils();
private:
std::unique_ptr<AppKit> m_appkit;
static MacUtils* m_instance;
void* self;
Q_DISABLE_COPY(MacUtils)
};
inline MacUtils* macUtils()
{
return MacUtils::instance();
}
#endif // KEEPASSXC_MACUTILS_H

View File

@ -30,7 +30,11 @@ PopupHelpWidget::PopupHelpWidget(QWidget* parent)
{
Q_ASSERT(parent);
#ifdef Q_OS_MACOS
setWindowFlags(Qt::FramelessWindowHint | Qt::Drawer);
#else
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
#endif
hide();
m_appWindow->installEventFilter(this);

View File

@ -34,6 +34,7 @@ NewDatabaseWizard::NewDatabaseWizard(QWidget* parent)
{
setWizardStyle(QWizard::MacStyle);
setOption(QWizard::WizardOption::HaveHelpButton, false);
setOption(QWizard::WizardOption::NoDefaultButton, false); // Needed for macOS
// clang-format off
m_pages << new NewDatabaseWizardPageMetaData()

View File

@ -32,14 +32,6 @@ if(WITH_XC_BROWSER)
RUNTIME DESTINATION ${PROXY_INSTALL_DIR} COMPONENT Runtime)
if(APPLE AND WITH_APP_BUNDLE)
set(PROXY_BINARY_DIR "${CMAKE_BINARY_DIR}/src/proxy/keepassxc-proxy")
set(PROXY_APP_DIR "KeePassXC.app/Contents/MacOS/keepassxc-proxy")
add_custom_command(TARGET keepassxc-proxy
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${PROXY_BINARY_DIR} ${PROXY_APP_DIR}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Copying keepassxc-proxy inside the application")
add_custom_command(TARGET keepassxc-proxy
POST_BUILD
COMMAND ${CMAKE_INSTALL_NAME_TOOL}
@ -51,8 +43,7 @@ if(WITH_XC_BROWSER)
"@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork"
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork
"@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork"
${PROXY_APP_DIR}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
keepassxc-proxy
COMMENT "Changing linking of keepassxc-proxy")
endif()
if(MINGW)

View File

@ -26,6 +26,9 @@ AgentSettingsWidget::AgentSettingsWidget(QWidget* parent)
, m_ui(new Ui::AgentSettingsWidget())
{
m_ui->setupUi(this);
#ifndef Q_OS_WIN
m_ui->useOpenSSHCheckBox->setVisible(false);
#endif
}
AgentSettingsWidget::~AgentSettingsWidget()
@ -35,9 +38,15 @@ AgentSettingsWidget::~AgentSettingsWidget()
void AgentSettingsWidget::loadSettings()
{
m_ui->enableSSHAgentCheckBox->setChecked(config()->get("SSHAgent", false).toBool());
#ifdef Q_OS_WIN
m_ui->useOpenSSHCheckBox->setChecked(config()->get("SSHAgentOpenSSH", false).toBool());
#endif
}
void AgentSettingsWidget::saveSettings()
{
config()->set("SSHAgent", m_ui->enableSSHAgentCheckBox->isChecked());
#ifdef Q_OS_WIN
config()->set("SSHAgentOpenSSH", m_ui->useOpenSSHCheckBox->isChecked());
#endif
}

View File

@ -30,6 +30,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useOpenSSHCheckBox">
<property name="text">
<string>Use OpenSSH for Windows instead of Pageant</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@ -21,10 +21,11 @@
#include "crypto/ssh/OpenSSHKey.h"
#include "crypto/ssh/BinaryStream.h"
#include "sshagent/KeeAgentSettings.h"
#include "core/Config.h"
#ifndef Q_OS_WIN
#include <QtNetwork>
#else
#ifdef Q_OS_WIN
#include <windows.h>
#endif
@ -35,6 +36,8 @@ SSHAgent::SSHAgent(QObject* parent)
{
#ifndef Q_OS_WIN
m_socketPath = QProcessEnvironment::systemEnvironment().value("SSH_AUTH_SOCK");
#else
m_socketPath = "\\\\.\\pipe\\openssh-ssh-agent";
#endif
}
@ -72,13 +75,22 @@ bool SSHAgent::isAgentRunning() const
#ifndef Q_OS_WIN
return !m_socketPath.isEmpty();
#else
return (FindWindowA("Pageant", "Pageant") != nullptr);
if (!config()->get("SSHAgentOpenSSH").toBool()) {
return (FindWindowA("Pageant", "Pageant") != nullptr);
} else {
return WaitNamedPipe(m_socketPath.toLatin1().data(), 100);
}
#endif
}
bool SSHAgent::sendMessage(const QByteArray& in, QByteArray& out)
{
#ifndef Q_OS_WIN
#ifdef Q_OS_WIN
if (!config()->get("SSHAgentOpenSSH").toBool()) {
return sendMessagePageant(in, out);
}
#endif
QLocalSocket socket;
BinaryStream stream(&socket);
@ -99,7 +111,11 @@ bool SSHAgent::sendMessage(const QByteArray& in, QByteArray& out)
socket.close();
return true;
#else
}
#ifdef Q_OS_WIN
bool SSHAgent::sendMessagePageant(const QByteArray& in, QByteArray& out)
{
HWND hWnd = FindWindowA("Pageant", "Pageant");
if (!hWnd) {
@ -159,8 +175,8 @@ bool SSHAgent::sendMessage(const QByteArray& in, QByteArray& out)
CloseHandle(handle);
return (res > 0);
#endif
}
#endif
/**
* Add the identity to the SSH agent.

View File

@ -62,12 +62,14 @@ private:
~SSHAgent();
bool sendMessage(const QByteArray& in, QByteArray& out);
#ifdef Q_OS_WIN
bool sendMessagePageant(const QByteArray& in, QByteArray& out);
#endif
static SSHAgent* m_instance;
#ifndef Q_OS_WIN
QString m_socketPath;
#else
#ifdef Q_OS_WIN
const quint32 AGENT_MAX_MSGLEN = 8192;
const quint32 AGENT_COPYDATA_ID = 0x804e50ba;
#endif

View File

@ -148,7 +148,7 @@ void TestAutoType::testSingleAutoType()
void TestAutoType::testGlobalAutoTypeWithNoMatch()
{
m_test->setActiveWindowTitle("nomatch");
MessageBox::setNextAnswer(QMessageBox::Ok);
MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString());
@ -195,7 +195,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
{
m_test->setActiveWindowTitle("An Entry Title!");
MessageBox::setNextAnswer(QMessageBox::Ok);
MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString());
@ -379,4 +379,4 @@ void TestAutoType::testAutoTypeEffectiveSequences()
QCOMPARE(entry5->effectiveAutoTypeSequence(), QString());
QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan);
QCOMPARE(entry6->effectiveAutoTypeSequence(), QString());
}
}

View File

@ -34,6 +34,7 @@
#include "cli/Add.h"
#include "cli/Clip.h"
#include "cli/Command.h"
#include "cli/Create.h"
#include "cli/Diceware.h"
#include "cli/Edit.h"
#include "cli/Estimate.h"
@ -137,9 +138,10 @@ QSharedPointer<Database> TestCli::readTestDatabase() const
void TestCli::testCommand()
{
QCOMPARE(Command::getCommands().size(), 12);
QCOMPARE(Command::getCommands().size(), 13);
QVERIFY(Command::getCommand("add"));
QVERIFY(Command::getCommand("clip"));
QVERIFY(Command::getCommand("create"));
QVERIFY(Command::getCommand("diceware"));
QVERIFY(Command::getCommand("edit"));
QVERIFY(Command::getCommand("estimate"));
@ -274,7 +276,7 @@ void TestCli::testClip()
// clang-format off
QFuture<void> future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"});
// clang-format on
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500);
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500);
@ -296,6 +298,76 @@ void TestCli::testClip()
QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry with path /Sample Entry has no TOTP set up.\n"));
}
void TestCli::testCreate()
{
Create createCmd;
QVERIFY(!createCmd.name.isEmpty());
QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name));
QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir());
QString databaseFilename = testDir->path() + "testCreate1.kdbx";
// Password
Utils::Test::setNextPassword("a");
createCmd.execute({"create", databaseFilename});
m_stderrFile->reset();
m_stdoutFile->reset();
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Insert password to encrypt database (Press enter to leave blank): \n"));
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
Utils::Test::setNextPassword("a");
auto db = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename, "", Utils::DEVNULL));
QVERIFY(db);
// Should refuse to create the database if it already exists.
qint64 pos = m_stdoutFile->pos();
qint64 errPos = m_stderrFile->pos();
createCmd.execute({"create", databaseFilename});
m_stdoutFile->seek(pos);
m_stderrFile->seek(errPos);
// Output should be empty when there is an error.
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QString errorMessage = QString("File " + databaseFilename + " already exists.\n");
QCOMPARE(m_stderrFile->readAll(), errorMessage.toUtf8());
// Testing with keyfile creation
QString databaseFilename2 = testDir->path() + "testCreate2.kdbx";
QString keyfilePath = testDir->path() + "keyfile.txt";
pos = m_stdoutFile->pos();
errPos = m_stderrFile->pos();
Utils::Test::setNextPassword("a");
createCmd.execute({"create", databaseFilename2, "-k", keyfilePath});
m_stdoutFile->seek(pos);
m_stderrFile->seek(errPos);
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Insert password to encrypt database (Press enter to leave blank): \n"));
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
Utils::Test::setNextPassword("a");
auto db2 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename2, keyfilePath, Utils::DEVNULL));
QVERIFY(db2);
// Testing with existing keyfile
QString databaseFilename3 = testDir->path() + "testCreate3.kdbx";
pos = m_stdoutFile->pos();
errPos = m_stderrFile->pos();
Utils::Test::setNextPassword("a");
createCmd.execute({"create", databaseFilename3, "-k", keyfilePath});
m_stdoutFile->seek(pos);
m_stderrFile->seek(errPos);
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Insert password to encrypt database (Press enter to leave blank): \n"));
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
Utils::Test::setNextPassword("a");
auto db3 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename3, keyfilePath, Utils::DEVNULL));
QVERIFY(db3);
}
void TestCli::testDiceware()
{
Diceware dicewareCmd;
@ -488,26 +560,11 @@ void TestCli::testEstimate()
QTextStream out(m_stdoutFile.data());
in << input << endl;
auto inEnd = in.pos();
in.seek(0);
estimateCmd.execute({"estimate"});
auto outEnd = out.pos();
estimateCmd.execute({"estimate", "-a"});
out.seek(0);
auto result = out.readAll();
QVERIFY(result.startsWith("Length " + length));
QVERIFY(result.contains("Entropy " + entropy));
QVERIFY(result.contains("Log10 " + log10));
// seek to end of stream
in.seek(inEnd);
out.seek(outEnd);
in << input << endl;
in.seek(inEnd);
estimateCmd.execute({"estimate", "-a"});
out.seek(outEnd);
result = out.readAll();
QVERIFY(result.startsWith("Length " + length));
QVERIFY(result.contains("Length " + length));
QVERIFY(result.contains("Entropy " + entropy));
QVERIFY(result.contains("Log10 " + log10));
for (const auto& string : asConst(searchStrings)) {

View File

@ -43,6 +43,7 @@ private slots:
void testCommand();
void testAdd();
void testClip();
void testCreate();
void testDiceware();
void testEdit();
void testEstimate_data();

View File

@ -134,10 +134,10 @@ void TestGui::cleanup()
{
// DO NOT save the database
m_db->markAsClean();
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
QApplication::processEvents();
MessageBox::setNextAnswer(QMessageBox::NoButton);
MessageBox::setNextAnswer(MessageBox::NoButton);
if (m_dbWidget) {
delete m_dbWidget;
@ -204,7 +204,7 @@ void TestGui::testCreateDatabase()
QCOMPARE(m_db->key()->rawKey(), compositeKey->rawKey());
// close the new database
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
}
@ -338,13 +338,13 @@ void TestGui::testAutoreloadDatabase()
mergeDbFile.close();
// Test accepting new file in autoreload
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Yes);
// Overwrite the current database with the temp data
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
m_dbFile->close();
Tools::wait(800);
QTRY_VERIFY(m_db != m_dbWidget->database());
m_db = m_dbWidget->database();
// the General group contains one entry from the new db data
@ -356,18 +356,15 @@ void TestGui::testAutoreloadDatabase()
init();
// Test rejecting new file in autoreload
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
// Overwrite the current temp database with a new file
m_dbFile->open();
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
m_dbFile->close();
Tools::wait(800);
m_db = m_dbWidget->database();
// Ensure the merge did not take place
QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 0);
QVERIFY(m_tabWidget->tabName(m_tabWidget->currentIndex()).endsWith("*"));
QTRY_VERIFY(m_tabWidget->tabName(m_tabWidget->currentIndex()).endsWith("*"));
// Reset the state
cleanup();
@ -380,13 +377,13 @@ void TestGui::testAutoreloadDatabase()
testEditEntry();
// This is saying yes to merging the entries
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Merge);
// Overwrite the current database with the temp data
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
m_dbFile->close();
Tools::wait(800);
QTRY_VERIFY(m_db != m_dbWidget->database());
m_db = m_dbWidget->database();
QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1);
@ -404,6 +401,9 @@ void TestGui::testEditEntry()
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
entryView->setFocus();
QVERIFY(entryView->hasFocus());
// Select the first entry in the database
QModelIndex entryItem = entryView->model()->index(0, 1);
Entry* entry = entryView->entryFromIndex(entryItem);
@ -604,7 +604,7 @@ void TestGui::testAddEntry()
// Add entry "something 5" but click cancel button (does NOT add entry)
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QTest::keyClicks(titleEdit, "something 5");
MessageBox::setNextAnswer(QMessageBox::Discard);
MessageBox::setNextAnswer(MessageBox::Discard);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton);
QApplication::processEvents();
@ -937,6 +937,7 @@ void TestGui::testDeleteEntry()
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction);
entryView->setFocus();
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::ViewMode);
clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton);
@ -944,7 +945,7 @@ void TestGui::testDeleteEntry()
QVERIFY(entryDeleteWidget->isEnabled());
QVERIFY(!m_db->metadata()->recycleBin());
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Move);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 3);
@ -954,12 +955,12 @@ void TestGui::testDeleteEntry()
clickIndex(entryView->model()->index(2, 1), entryView, Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(entryView->selectionModel()->selectedRows().size(), 2);
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::Cancel);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 3);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1);
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Move);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 1);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3);
@ -972,19 +973,19 @@ void TestGui::testDeleteEntry()
QCOMPARE(groupView->currentGroup()->name(), m_db->metadata()->recycleBin()->name());
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::Cancel);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 3);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3);
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Delete);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 2);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 2);
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton, Qt::ControlModifier);
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Delete);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 0);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 0);
@ -996,6 +997,7 @@ void TestGui::testDeleteEntry()
void TestGui::testCloneEntry()
{
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
entryView->setFocus();
QCOMPARE(entryView->model()->rowCount(), 1);
@ -1183,7 +1185,7 @@ void TestGui::testKeePass1Import()
QTRY_COMPARE(m_tabWidget->tabName(m_tabWidget->currentIndex()), QString("basic [New Database]*"));
// Close the KeePass1 Database
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
QApplication::processEvents();
}
@ -1192,7 +1194,7 @@ void TestGui::testDatabaseLocking()
{
QString origDbName = m_tabWidget->tabText(0);
MessageBox::setNextAnswer(QMessageBox::Cancel);
MessageBox::setNextAnswer(MessageBox::Cancel);
triggerAction("actionLockDatabases");
QCOMPARE(m_tabWidget->tabName(0), origDbName + " [Locked]");
@ -1248,7 +1250,7 @@ void TestGui::testDragAndDropKdbxFiles()
QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1);
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
QTRY_COMPARE(m_tabWidget->count(), openedDatabasesCount);