mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge branch 'develop'
Conflicts: src/core/Tools.cpp src/sshagent/SSHAgent.cpp
This commit is contained in:
commit
21de6f6163
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
175
src/cli/Create.cpp
Normal 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
39
src/cli/Create.h
Normal 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
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
void bootstrap();
|
||||
void bootstrapApplication();
|
||||
void restoreMainWindowState(MainWindow& mainWindow);
|
||||
void disableCoreDumps();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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)));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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()));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ signals:
|
||||
void importKeePass1Database();
|
||||
void importCsv();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void openDatabaseFromFile(QListWidgetItem* item);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
62
src/gui/macutils/MacUtils.cpp
Normal file
62
src/gui/macutils/MacUtils.cpp
Normal 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());
|
||||
}
|
56
src/gui/macutils/MacUtils.h
Normal file
56
src/gui/macutils/MacUtils.h
Normal 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
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -43,6 +43,7 @@ private slots:
|
||||
void testCommand();
|
||||
void testAdd();
|
||||
void testClip();
|
||||
void testCreate();
|
||||
void testDiceware();
|
||||
void testEdit();
|
||||
void testEstimate_data();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user