mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-26 15:59:50 -05:00
Merge branch 'release/2.5.2' into develop
This commit is contained in:
commit
2fab4d576a
@ -36,6 +36,7 @@ endif (CCACHE_FOUND)
|
|||||||
|
|
||||||
# Support Visual Studio Code
|
# Support Visual Studio Code
|
||||||
include(CMakeToolsHelpers OPTIONAL)
|
include(CMakeToolsHelpers OPTIONAL)
|
||||||
|
include(FeatureSummary)
|
||||||
|
|
||||||
include(CheckCCompilerFlag)
|
include(CheckCCompilerFlag)
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
@ -465,8 +466,6 @@ endif()
|
|||||||
|
|
||||||
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
|
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
|
||||||
|
|
||||||
include(FeatureSummary)
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(share)
|
add_subdirectory(share)
|
||||||
if(WITH_TESTS)
|
if(WITH_TESTS)
|
||||||
|
5
COPYING
5
COPYING
@ -248,3 +248,8 @@ Comment: from Freedesktop.org website
|
|||||||
|
|
||||||
Files: share/icons/application/32x32/actions/statistics.png
|
Files: share/icons/application/32x32/actions/statistics.png
|
||||||
Copyright: Icon made by Freepik from https://www.flaticon.com/free-icon/bars-chart_265733
|
Copyright: Icon made by Freepik from https://www.flaticon.com/free-icon/bars-chart_265733
|
||||||
|
|
||||||
|
Files: share/icons/application/scalable/actions/object-locked.svg
|
||||||
|
share/icons/application/scalable/actions/object-unlocked.svg
|
||||||
|
License: LGPL-3
|
||||||
|
Comment: from Breeze icon theme (https://github.com/KDE/breeze-icons)
|
||||||
|
@ -117,7 +117,7 @@ Displays the program version.
|
|||||||
.IP "-d, --dry-run <path>"
|
.IP "-d, --dry-run <path>"
|
||||||
Prints the changes detected by the merge operation without making any changes to the database.
|
Prints the changes detected by the merge operation without making any changes to the database.
|
||||||
|
|
||||||
.IP "-f, --key-file-from <path>"
|
.IP "--key-file-from <path>"
|
||||||
Sets the path of the key file for the second database.
|
Sets the path of the key file for the second database.
|
||||||
|
|
||||||
.IP "--no-password-from"
|
.IP "--no-password-from"
|
||||||
|
14
share/icons/application/scalable/actions/object-locked.svg
Normal file
14
share/icons/application/scalable/actions/object-locked.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#232629;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 11,3 C 8.784,3 7,4.784 7,7 l 0,4 -2,0 c 0,2.666667 0,5.333333 0,8 4,0 8,0 12,0 l 0,-8 c -0.666667,0 -1.333333,0 -2,0 L 15,7 C 15,4.784 13.216,3 11,3 m 0,1 c 1.662,0 3,1.561 3,3.5 L 14,11 8,11 8,7.5 C 8,5.561 9.338,4 11,4"
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
transform="translate(1,1)"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 592 B |
15
share/icons/application/scalable/actions/object-unlocked.svg
Normal file
15
share/icons/application/scalable/actions/object-unlocked.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#232629;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="m11 3c-2.216 0-4 1.784-4 4v1h1v-.5c0-1.939 1.338-3.5 3-3.5 1.662 0 3 1.561 3 3.5v3.5h-5-1-1-1-1v1 7h1 10 1v-8h-1-1v-4c0-2.216-1.784-4-4-4m-5 9h10v6h-10v-6"
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
transform="translate(1,1)"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 523 B |
@ -121,6 +121,7 @@ set(keepassx_SOURCES
|
|||||||
gui/TotpDialog.cpp
|
gui/TotpDialog.cpp
|
||||||
gui/TotpExportSettingsDialog.cpp
|
gui/TotpExportSettingsDialog.cpp
|
||||||
gui/DatabaseOpenDialog.cpp
|
gui/DatabaseOpenDialog.cpp
|
||||||
|
gui/URLEdit.cpp
|
||||||
gui/WelcomeWidget.cpp
|
gui/WelcomeWidget.cpp
|
||||||
gui/csvImport/CsvImportWidget.cpp
|
gui/csvImport/CsvImportWidget.cpp
|
||||||
gui/csvImport/CsvImportWizard.cpp
|
gui/csvImport/CsvImportWizard.cpp
|
||||||
|
@ -42,7 +42,7 @@ QJsonObject BrowserAction::readResponse(const QJsonObject& json)
|
|||||||
|
|
||||||
bool triggerUnlock = false;
|
bool triggerUnlock = false;
|
||||||
const QString trigger = json.value("triggerUnlock").toString();
|
const QString trigger = json.value("triggerUnlock").toString();
|
||||||
if (!trigger.isEmpty() && trigger.compare("true", Qt::CaseSensitive) == 0) {
|
if (!trigger.isEmpty() && trigger.compare(TRUE_STR, Qt::CaseSensitive) == 0) {
|
||||||
triggerUnlock = true;
|
triggerUnlock = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
|||||||
const QString id = decrypted.value("id").toString();
|
const QString id = decrypted.value("id").toString();
|
||||||
const QString submit = decrypted.value("submitUrl").toString();
|
const QString submit = decrypted.value("submitUrl").toString();
|
||||||
const QString auth = decrypted.value("httpAuth").toString();
|
const QString auth = decrypted.value("httpAuth").toString();
|
||||||
const bool httpAuth = auth.compare("true", Qt::CaseSensitive) == 0 ? true : false;
|
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
|
||||||
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList, httpAuth);
|
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList, httpAuth);
|
||||||
|
|
||||||
if (users.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
@ -469,7 +469,7 @@ QJsonObject BrowserAction::buildMessage(const QString& nonce) const
|
|||||||
{
|
{
|
||||||
QJsonObject message;
|
QJsonObject message;
|
||||||
message["version"] = KEEPASSXC_VERSION;
|
message["version"] = KEEPASSXC_VERSION;
|
||||||
message["success"] = "true";
|
message["success"] = TRUE_STR;
|
||||||
message["nonce"] = nonce;
|
message["nonce"] = nonce;
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwo
|
|||||||
// Extra entry related options saved in custom data
|
// Extra entry related options saved in custom data
|
||||||
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
|
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
|
||||||
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
|
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
|
||||||
|
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
|
||||||
// Multiple URL's
|
// Multiple URL's
|
||||||
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
|
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
|
||||||
|
|
||||||
@ -382,7 +383,12 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
|||||||
QList<Entry*> pwEntries;
|
QList<Entry*> pwEntries;
|
||||||
for (auto* entry : searchEntries(url, submitUrl, keyList)) {
|
for (auto* entry : searchEntries(url, submitUrl, keyList)) {
|
||||||
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
|
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
|
||||||
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
|
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!httpAuth && entry->customData()->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)
|
||||||
|
&& entry->customData()->value(BrowserService::OPTION_ONLY_HTTP_AUTH) == TRUE_STR) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,12 +608,10 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search for additional URL's starting with KP2A_URL
|
// Search for additional URL's starting with KP2A_URL
|
||||||
if (entry->attributes()->keys().contains(ADDITIONAL_URL)) {
|
for (const auto& key : entry->attributes()->keys()) {
|
||||||
for (const auto& key : entry->attributes()->keys()) {
|
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), url, submitUrl)) {
|
||||||
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), url, submitUrl)) {
|
entries.append(entry);
|
||||||
entries.append(entry);
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -852,7 +856,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entry->isExpired()) {
|
if (entry->isExpired()) {
|
||||||
res["expired"] = "true";
|
res["expired"] = TRUE_STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry->customData()->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
|
if (entry->customData()->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
|
||||||
|
@ -74,6 +74,7 @@ public:
|
|||||||
static const QString LEGACY_ASSOCIATE_KEY_PREFIX;
|
static const QString LEGACY_ASSOCIATE_KEY_PREFIX;
|
||||||
static const QString OPTION_SKIP_AUTO_SUBMIT;
|
static const QString OPTION_SKIP_AUTO_SUBMIT;
|
||||||
static const QString OPTION_HIDE_ENTRY;
|
static const QString OPTION_HIDE_ENTRY;
|
||||||
|
static const QString OPTION_ONLY_HTTP_AUTH;
|
||||||
static const QString ADDITIONAL_URL;
|
static const QString ADDITIONAL_URL;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -30,8 +30,7 @@ const QCommandLineOption Merge::SameCredentialsOption =
|
|||||||
QObject::tr("Use the same credentials for both database files."));
|
QObject::tr("Use the same credentials for both database files."));
|
||||||
|
|
||||||
const QCommandLineOption Merge::KeyFileFromOption =
|
const QCommandLineOption Merge::KeyFileFromOption =
|
||||||
QCommandLineOption(QStringList() << "k"
|
QCommandLineOption(QStringList() << "key-file-from",
|
||||||
<< "key-file-from",
|
|
||||||
QObject::tr("Key file of the database to merge from."),
|
QObject::tr("Key file of the database to merge from."),
|
||||||
QObject::tr("path"));
|
QObject::tr("path"));
|
||||||
|
|
||||||
|
@ -149,8 +149,7 @@ void enterInteractiveMode(const QStringList& arguments)
|
|||||||
prompt += "> ";
|
prompt += "> ";
|
||||||
command = reader->readLine(prompt);
|
command = reader->readLine(prompt);
|
||||||
if (reader->isFinished()) {
|
if (reader->isFinished()) {
|
||||||
currentDatabase->releaseData();
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList args = Utils::splitCommandString(command);
|
QStringList args = Utils::splitCommandString(command);
|
||||||
@ -163,14 +162,17 @@ void enterInteractiveMode(const QStringList& arguments)
|
|||||||
errorTextStream << QObject::tr("Unknown command %1").arg(args[0]) << "\n";
|
errorTextStream << QObject::tr("Unknown command %1").arg(args[0]) << "\n";
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd->name == "quit" || cmd->name == "exit") {
|
} else if (cmd->name == "quit" || cmd->name == "exit") {
|
||||||
currentDatabase->releaseData();
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd->currentDatabase = currentDatabase;
|
cmd->currentDatabase = currentDatabase;
|
||||||
cmd->execute(args);
|
cmd->execute(args);
|
||||||
currentDatabase = cmd->currentDatabase;
|
currentDatabase = cmd->currentDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentDatabase) {
|
||||||
|
currentDatabase->releaseData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#ifndef KEEPASSX_GLOBAL_H
|
#ifndef KEEPASSX_GLOBAL_H
|
||||||
#define KEEPASSX_GLOBAL_H
|
#define KEEPASSX_GLOBAL_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
@ -42,6 +43,9 @@
|
|||||||
#define FILE_CASE_SENSITIVE Qt::CaseSensitive
|
#define FILE_CASE_SENSITIVE Qt::CaseSensitive
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const auto TRUE_STR = QStringLiteral("true");
|
||||||
|
static const auto FALSE_STR = QStringLiteral("false");
|
||||||
|
|
||||||
template <typename T> struct AddConst
|
template <typename T> struct AddConst
|
||||||
{
|
{
|
||||||
typedef const T Type;
|
typedef const T Type;
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
|
#include <QUrl>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
@ -259,6 +260,33 @@ namespace Tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkUrlValid(const QString& urlField)
|
||||||
|
{
|
||||||
|
if (urlField.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url;
|
||||||
|
if (urlField.contains("://")) {
|
||||||
|
url = urlField;
|
||||||
|
} else {
|
||||||
|
url = QUrl::fromUserInput(urlField);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.scheme() != "file" && url.host().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for illegal characters. Adds also the wildcard * to the list
|
||||||
|
QRegularExpression re("[<>\\^`{|}\\*]");
|
||||||
|
auto match = re.match(urlField);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Escape common regex symbols except for *, ?, and |
|
// Escape common regex symbols except for *, ?, and |
|
||||||
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
|
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ namespace Tools
|
|||||||
bool isBase64(const QByteArray& ba);
|
bool isBase64(const QByteArray& ba);
|
||||||
void sleep(int ms);
|
void sleep(int ms);
|
||||||
void wait(int ms);
|
void wait(int ms);
|
||||||
|
bool checkUrlValid(const QString& urlField);
|
||||||
QString uuidToHex(const QUuid& uuid);
|
QString uuidToHex(const QUuid& uuid);
|
||||||
QUuid hexToUuid(const QString& uuid);
|
QUuid hexToUuid(const QString& uuid);
|
||||||
QRegularExpression convertToRegex(const QString& string,
|
QRegularExpression convertToRegex(const QString& string,
|
||||||
|
@ -4,6 +4,7 @@ if(WITH_XC_FDOSECRETS)
|
|||||||
add_library(fdosecrets STATIC
|
add_library(fdosecrets STATIC
|
||||||
# app settings page
|
# app settings page
|
||||||
FdoSecretsPlugin.cpp
|
FdoSecretsPlugin.cpp
|
||||||
|
widgets/SettingsModels.cpp
|
||||||
widgets/SettingsWidgetFdoSecrets.cpp
|
widgets/SettingsWidgetFdoSecrets.cpp
|
||||||
|
|
||||||
# per database settings page
|
# per database settings page
|
||||||
|
@ -60,11 +60,15 @@ void FdoSecretsPlugin::updateServiceState()
|
|||||||
});
|
});
|
||||||
if (!m_secretService->initialize()) {
|
if (!m_secretService->initialize()) {
|
||||||
m_secretService.reset();
|
m_secretService.reset();
|
||||||
|
FdoSecrets::settings()->setEnabled(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
emit secretServiceStarted();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (m_secretService) {
|
if (m_secretService) {
|
||||||
m_secretService.reset();
|
m_secretService.reset();
|
||||||
|
emit secretServiceStopped();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,6 +78,11 @@ Service* FdoSecretsPlugin::serviceInstance() const
|
|||||||
return m_secretService.data();
|
return m_secretService.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatabaseTabWidget* FdoSecretsPlugin::dbTabs() const
|
||||||
|
{
|
||||||
|
return m_dbTabs;
|
||||||
|
}
|
||||||
|
|
||||||
void FdoSecretsPlugin::emitRequestSwitchToDatabases()
|
void FdoSecretsPlugin::emitRequestSwitchToDatabases()
|
||||||
{
|
{
|
||||||
emit requestSwitchToDatabases();
|
emit requestSwitchToDatabases();
|
||||||
|
@ -59,6 +59,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
FdoSecrets::Service* serviceInstance() const;
|
FdoSecrets::Service* serviceInstance() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The db tabs widget, containing opened databases. Can be nullptr.
|
||||||
|
*/
|
||||||
|
DatabaseTabWidget* dbTabs() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void emitRequestSwitchToDatabases();
|
void emitRequestSwitchToDatabases();
|
||||||
void emitRequestShowNotification(const QString& msg, const QString& title = {});
|
void emitRequestShowNotification(const QString& msg, const QString& title = {});
|
||||||
@ -67,6 +72,8 @@ signals:
|
|||||||
void error(const QString& msg);
|
void error(const QString& msg);
|
||||||
void requestSwitchToDatabases();
|
void requestSwitchToDatabases();
|
||||||
void requestShowNotification(const QString& msg, const QString& title, int msTimeoutHint);
|
void requestShowNotification(const QString& msg, const QString& title, int msTimeoutHint);
|
||||||
|
void secretServiceStarted();
|
||||||
|
void secretServiceStopped();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<DatabaseTabWidget> m_dbTabs;
|
QPointer<DatabaseTabWidget> m_dbTabs;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "fdosecrets/objects/Item.h"
|
#include "fdosecrets/objects/Item.h"
|
||||||
#include "fdosecrets/objects/Prompt.h"
|
#include "fdosecrets/objects/Prompt.h"
|
||||||
#include "fdosecrets/objects/Service.h"
|
#include "fdosecrets/objects/Service.h"
|
||||||
|
#include "fdosecrets/objects/Session.h"
|
||||||
|
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
@ -284,8 +285,13 @@ namespace FdoSecrets
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pathToObject<Session>(secret.session)) {
|
||||||
|
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
|
||||||
|
}
|
||||||
|
|
||||||
prompt = nullptr;
|
prompt = nullptr;
|
||||||
|
|
||||||
|
bool newlyCreated = true;
|
||||||
Item* item = nullptr;
|
Item* item = nullptr;
|
||||||
QString itemPath;
|
QString itemPath;
|
||||||
StringStringMap attributes;
|
StringStringMap attributes;
|
||||||
@ -303,6 +309,7 @@ namespace FdoSecrets
|
|||||||
}
|
}
|
||||||
if (!existings.value().isEmpty() && replace) {
|
if (!existings.value().isEmpty() && replace) {
|
||||||
item = existings.value().front();
|
item = existings.value().front();
|
||||||
|
newlyCreated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,10 +344,16 @@ namespace FdoSecrets
|
|||||||
|
|
||||||
ret = item->setProperties(properties);
|
ret = item->setProperties(properties);
|
||||||
if (ret.isError()) {
|
if (ret.isError()) {
|
||||||
|
if (newlyCreated) {
|
||||||
|
item->doDelete();
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
ret = item->setSecret(secret);
|
ret = item->setSecret(secret);
|
||||||
if (ret.isError()) {
|
if (ret.isError()) {
|
||||||
|
if (newlyCreated) {
|
||||||
|
item->doDelete();
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,14 +469,19 @@ namespace FdoSecrets
|
|||||||
|
|
||||||
// Attach signal to update exposed group settings if the group was removed.
|
// Attach signal to update exposed group settings if the group was removed.
|
||||||
//
|
//
|
||||||
// The lifetime of the connection is bound to the database object, because
|
// When the group object is normally deleted due to ~Database, the databaseReplaced
|
||||||
// in Database::~Database, groups are also deleted as children, but we don't
|
// signal should be first emitted, and we will clean up connection in reloadDatabase,
|
||||||
// want to trigger this.
|
// so this handler won't be triggered.
|
||||||
// This works because the fact that QObject disconnects signals BEFORE deleting
|
|
||||||
// children.
|
|
||||||
QPointer<Database> db = m_backend->database().data();
|
QPointer<Database> db = m_backend->database().data();
|
||||||
connect(m_exposedGroup.data(), &Group::groupAboutToRemove, db, [db](Group* toBeRemoved) {
|
connect(m_exposedGroup.data(), &Group::groupAboutToRemove, this, [this](Group* toBeRemoved) {
|
||||||
if (!db) {
|
if (backendLocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto db = m_backend->database();
|
||||||
|
if (toBeRemoved->database() != db) {
|
||||||
|
// should not happen, but anyway.
|
||||||
|
// somehow our current database has been changed, and the old group is being deleted
|
||||||
|
// possibly logic changes in replaceDatabase.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto uuid = FdoSecrets::settings()->exposedGroup(db);
|
auto uuid = FdoSecrets::settings()->exposedGroup(db);
|
||||||
@ -483,7 +501,7 @@ namespace FdoSecrets
|
|||||||
|
|
||||||
// Monitor exposed group settings
|
// Monitor exposed group settings
|
||||||
connect(m_backend->database()->metadata()->customData(), &CustomData::customDataModified, this, [this]() {
|
connect(m_backend->database()->metadata()->customData(), &CustomData::customDataModified, this, [this]() {
|
||||||
if (!m_exposedGroup || !m_backend) {
|
if (!m_exposedGroup || backendLocked()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_exposedGroup->uuid() == FdoSecrets::settings()->exposedGroup(m_backend->database())) {
|
if (m_exposedGroup->uuid() == FdoSecrets::settings()->exposedGroup(m_backend->database())) {
|
||||||
@ -602,9 +620,13 @@ namespace FdoSecrets
|
|||||||
|
|
||||||
void Collection::cleanupConnections()
|
void Collection::cleanupConnections()
|
||||||
{
|
{
|
||||||
|
m_backend->database()->metadata()->customData()->disconnect(this);
|
||||||
if (m_exposedGroup) {
|
if (m_exposedGroup) {
|
||||||
m_exposedGroup->disconnect(this);
|
for (const auto group : m_exposedGroup->groupsRecursive(true)) {
|
||||||
|
group->disconnect(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_items.clear();
|
m_items.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,8 +681,8 @@ namespace FdoSecrets
|
|||||||
Q_ASSERT(m_backend);
|
Q_ASSERT(m_backend);
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
// just to be safe
|
// the root group's parent is nullptr, we treat it as not in recycle bin.
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_backend->database()->metadata()) {
|
if (!m_backend->database()->metadata()) {
|
||||||
|
@ -158,6 +158,12 @@ namespace FdoSecrets
|
|||||||
return std::move(m_value);
|
return std::move(m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value or handle the error by the passed in dbus object
|
||||||
|
* @tparam P
|
||||||
|
* @param p
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
template <typename P> T valueOrHandle(P* p) const&
|
template <typename P> T valueOrHandle(P* p) const&
|
||||||
{
|
{
|
||||||
if (isError()) {
|
if (isError()) {
|
||||||
@ -169,6 +175,12 @@ namespace FdoSecrets
|
|||||||
return m_value;
|
return m_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value or handle the error by the passed in dbus object
|
||||||
|
* @tparam P
|
||||||
|
* @param p
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
template <typename P> T&& valueOrHandle(P* p) &&
|
template <typename P> T&& valueOrHandle(P* p) &&
|
||||||
{
|
{
|
||||||
if (isError()) {
|
if (isError()) {
|
||||||
|
@ -47,7 +47,6 @@ namespace FdoSecrets
|
|||||||
, m_insdieEnsureDefaultAlias(false)
|
, m_insdieEnsureDefaultAlias(false)
|
||||||
, m_serviceWatcher(nullptr)
|
, m_serviceWatcher(nullptr)
|
||||||
{
|
{
|
||||||
registerWithPath(QStringLiteral(DBUS_PATH_SECRETS), new ServiceAdaptor(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Service::~Service()
|
Service::~Service()
|
||||||
@ -64,6 +63,8 @@ namespace FdoSecrets
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerWithPath(QStringLiteral(DBUS_PATH_SECRETS), new ServiceAdaptor(this));
|
||||||
|
|
||||||
// Connect to service unregistered signal
|
// Connect to service unregistered signal
|
||||||
m_serviceWatcher.reset(new QDBusServiceWatcher());
|
m_serviceWatcher.reset(new QDBusServiceWatcher());
|
||||||
connect(m_serviceWatcher.data(),
|
connect(m_serviceWatcher.data(),
|
||||||
|
396
src/fdosecrets/widgets/SettingsModels.cpp
Normal file
396
src/fdosecrets/widgets/SettingsModels.cpp
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SettingsModels.h"
|
||||||
|
|
||||||
|
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||||
|
#include "fdosecrets/FdoSecretsSettings.h"
|
||||||
|
#include "fdosecrets/objects/Service.h"
|
||||||
|
#include "fdosecrets/objects/Session.h"
|
||||||
|
|
||||||
|
#include "core/Database.h"
|
||||||
|
#include "core/DatabaseIcons.h"
|
||||||
|
#include "core/FilePath.h"
|
||||||
|
#include "gui/DatabaseTabWidget.h"
|
||||||
|
#include "gui/DatabaseWidget.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
namespace FdoSecrets
|
||||||
|
{
|
||||||
|
|
||||||
|
SettingsDatabaseModel::SettingsDatabaseModel(DatabaseTabWidget* dbTabs, QObject* parent)
|
||||||
|
: QAbstractTableModel(parent)
|
||||||
|
, m_dbTabs(nullptr)
|
||||||
|
{
|
||||||
|
setTabWidget(dbTabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDatabaseModel::setTabWidget(DatabaseTabWidget* dbTabs)
|
||||||
|
{
|
||||||
|
auto old = m_dbTabs;
|
||||||
|
m_dbTabs = dbTabs;
|
||||||
|
if (old != m_dbTabs) {
|
||||||
|
populateModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsDatabaseModel::rowCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_dbs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsDatabaseModel::columnCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if (orientation != Qt::Horizontal) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role != Qt::DisplayRole) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (section) {
|
||||||
|
case 0:
|
||||||
|
return tr("File Name");
|
||||||
|
case 1:
|
||||||
|
return tr("Group");
|
||||||
|
case 2:
|
||||||
|
return tr("Manage");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsDatabaseModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto& dbWidget = m_dbs[index.row()];
|
||||||
|
if (!dbWidget) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (index.column()) {
|
||||||
|
case 0:
|
||||||
|
return dataForName(dbWidget, role);
|
||||||
|
case 1:
|
||||||
|
return dataForExposedGroup(dbWidget, role);
|
||||||
|
case 2:
|
||||||
|
return dataForManage(dbWidget, role);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsDatabaseModel::dataForName(DatabaseWidget* db, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole: {
|
||||||
|
QFileInfo fi(db->database()->filePath());
|
||||||
|
return fi.fileName();
|
||||||
|
}
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
return db->database()->filePath();
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsDatabaseModel::dataForExposedGroup(DatabaseWidget* dbWidget, int role)
|
||||||
|
{
|
||||||
|
if (dbWidget->isLocked()) {
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return tr("Unlock to show");
|
||||||
|
case Qt::DecorationRole:
|
||||||
|
return filePath()->icon(QStringLiteral("apps"), QStringLiteral("object-locked"), true);
|
||||||
|
case Qt::FontRole: {
|
||||||
|
QFont font;
|
||||||
|
font.setItalic(true);
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto db = dbWidget->database();
|
||||||
|
auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db));
|
||||||
|
if (group) {
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return group->name();
|
||||||
|
case Qt::DecorationRole:
|
||||||
|
return group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
|
||||||
|
: group->iconScaledPixmap();
|
||||||
|
case Qt::FontRole:
|
||||||
|
if (group->isExpired()) {
|
||||||
|
QFont font;
|
||||||
|
font.setStrikeOut(true);
|
||||||
|
return font;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return tr("None");
|
||||||
|
case Qt::DecorationRole:
|
||||||
|
return filePath()->icon(QStringLiteral("apps"), QStringLiteral("paint-none"), true);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsDatabaseModel::dataForManage(DatabaseWidget* db, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::EditRole:
|
||||||
|
return QVariant::fromValue(db);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDatabaseModel::populateModel()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_dbs.clear();
|
||||||
|
|
||||||
|
if (m_dbTabs) {
|
||||||
|
// Add existing database tabs
|
||||||
|
for (int idx = 0; idx != m_dbTabs->count(); ++idx) {
|
||||||
|
auto dbWidget = m_dbTabs->databaseWidgetFromIndex(idx);
|
||||||
|
databaseAdded(dbWidget, false);
|
||||||
|
}
|
||||||
|
// connect signals
|
||||||
|
connect(m_dbTabs, &DatabaseTabWidget::databaseOpened, this, [this](DatabaseWidget* db) {
|
||||||
|
databaseAdded(db, true);
|
||||||
|
});
|
||||||
|
connect(m_dbTabs, &DatabaseTabWidget::databaseClosed, this, &SettingsDatabaseModel::databaseRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDatabaseModel::databaseAdded(DatabaseWidget* db, bool emitSignals)
|
||||||
|
{
|
||||||
|
int row = m_dbs.size();
|
||||||
|
if (emitSignals) {
|
||||||
|
beginInsertRows({}, row, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dbs.append(db);
|
||||||
|
connect(db, &DatabaseWidget::databaseLocked, this, [row, this]() {
|
||||||
|
emit dataChanged(index(row, 1), index(row, 2));
|
||||||
|
});
|
||||||
|
connect(db, &DatabaseWidget::databaseUnlocked, this, [row, this]() {
|
||||||
|
emit dataChanged(index(row, 1), index(row, 2));
|
||||||
|
});
|
||||||
|
connect(db, &DatabaseWidget::databaseModified, this, [row, this]() {
|
||||||
|
emit dataChanged(index(row, 0), index(row, 2));
|
||||||
|
});
|
||||||
|
connect(db, &DatabaseWidget::databaseFilePathChanged, this, [row, this]() {
|
||||||
|
emit dataChanged(index(row, 0), index(row, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (emitSignals) {
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDatabaseModel::databaseRemoved(const QString& filePath)
|
||||||
|
{
|
||||||
|
for (int i = 0; i != m_dbs.size(); i++) {
|
||||||
|
if (m_dbs[i] && m_dbs[i]->database()->filePath() == filePath) {
|
||||||
|
beginRemoveRows({}, i, i);
|
||||||
|
|
||||||
|
m_dbs[i]->disconnect(this);
|
||||||
|
m_dbs.removeAt(i);
|
||||||
|
|
||||||
|
endRemoveRows();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSessionModel::SettingsSessionModel(FdoSecretsPlugin* plugin, QObject* parent)
|
||||||
|
: QAbstractTableModel(parent)
|
||||||
|
, m_service(nullptr)
|
||||||
|
{
|
||||||
|
setService(plugin->serviceInstance());
|
||||||
|
connect(plugin, &FdoSecretsPlugin::secretServiceStarted, this, [plugin, this]() {
|
||||||
|
setService(plugin->serviceInstance());
|
||||||
|
});
|
||||||
|
connect(plugin, &FdoSecretsPlugin::secretServiceStopped, this, [this]() { setService(nullptr); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsSessionModel::setService(Service* service)
|
||||||
|
{
|
||||||
|
auto old = m_service;
|
||||||
|
m_service = service;
|
||||||
|
if (old != m_service) {
|
||||||
|
populateModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsSessionModel::rowCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_sessions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsSessionModel::columnCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsSessionModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if (orientation != Qt::Horizontal) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role != Qt::DisplayRole) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (section) {
|
||||||
|
case 0:
|
||||||
|
return tr("Application");
|
||||||
|
case 1:
|
||||||
|
return tr("Manage");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsSessionModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto& sess = m_sessions[index.row()];
|
||||||
|
if (!sess) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (index.column()) {
|
||||||
|
case 0:
|
||||||
|
return dataForApplication(sess, role);
|
||||||
|
case 1:
|
||||||
|
return dataForManage(sess, role);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsSessionModel::dataForApplication(Session* sess, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return sess->peer();
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SettingsSessionModel::dataForManage(Session* sess, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::EditRole: {
|
||||||
|
auto v = QVariant::fromValue(sess);
|
||||||
|
qDebug() << v << v.type() << v.userType();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsSessionModel::populateModel()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_sessions.clear();
|
||||||
|
|
||||||
|
if (m_service) {
|
||||||
|
// Add existing database tabs
|
||||||
|
for (const auto& sess : m_service->sessions()) {
|
||||||
|
sessionAdded(sess, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect signals
|
||||||
|
connect(m_service, &Service::sessionOpened, this, [this](Session* sess) { sessionAdded(sess, true); });
|
||||||
|
connect(m_service, &Service::sessionClosed, this, &SettingsSessionModel::sessionRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsSessionModel::sessionAdded(Session* sess, bool emitSignals)
|
||||||
|
{
|
||||||
|
int row = m_sessions.size();
|
||||||
|
if (emitSignals) {
|
||||||
|
beginInsertRows({}, row, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sessions.append(sess);
|
||||||
|
|
||||||
|
if (emitSignals) {
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsSessionModel::sessionRemoved(Session* sess)
|
||||||
|
{
|
||||||
|
for (int i = 0; i != m_sessions.size(); i++) {
|
||||||
|
if (m_sessions[i] == sess) {
|
||||||
|
beginRemoveRows({}, i, i);
|
||||||
|
|
||||||
|
m_sessions[i]->disconnect(this);
|
||||||
|
m_sessions.removeAt(i);
|
||||||
|
|
||||||
|
endRemoveRows();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FdoSecrets
|
96
src/fdosecrets/widgets/SettingsModels.h
Normal file
96
src/fdosecrets/widgets/SettingsModels.h
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
|
||||||
|
#define KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
|
||||||
|
|
||||||
|
#include <QAbstractTableModel>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
class DatabaseTabWidget;
|
||||||
|
class DatabaseWidget;
|
||||||
|
class FdoSecretsPlugin;
|
||||||
|
|
||||||
|
namespace FdoSecrets
|
||||||
|
{
|
||||||
|
class SettingsDatabaseModel : public QAbstractTableModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit SettingsDatabaseModel(DatabaseTabWidget* dbTabs, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void setTabWidget(DatabaseTabWidget* dbTabs);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex& parent) const override;
|
||||||
|
int columnCount(const QModelIndex& parent) const override;
|
||||||
|
QVariant data(const QModelIndex& index, int role) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVariant dataForName(DatabaseWidget* db, int role) const;
|
||||||
|
static QVariant dataForExposedGroup(DatabaseWidget* db, int role);
|
||||||
|
QVariant dataForManage(DatabaseWidget* db, int role) const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void populateModel();
|
||||||
|
void databaseAdded(DatabaseWidget* db, bool emitSignals);
|
||||||
|
void databaseRemoved(const QString& filePath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// source
|
||||||
|
QPointer<DatabaseTabWidget> m_dbTabs;
|
||||||
|
|
||||||
|
// internal store
|
||||||
|
QList<QPointer<DatabaseWidget>> m_dbs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Service;
|
||||||
|
class Session;
|
||||||
|
|
||||||
|
class SettingsSessionModel : public QAbstractTableModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit SettingsSessionModel(FdoSecretsPlugin* plugin, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex& parent) const override;
|
||||||
|
int columnCount(const QModelIndex& parent) const override;
|
||||||
|
QVariant data(const QModelIndex& index, int role) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setService(Service* service);
|
||||||
|
|
||||||
|
QVariant dataForApplication(Session* sess, int role) const;
|
||||||
|
QVariant dataForManage(Session* sess, int role) const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void populateModel();
|
||||||
|
void sessionAdded(Session* sess, bool emitSignals);
|
||||||
|
void sessionRemoved(Session* sess);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// source
|
||||||
|
QPointer<Service> m_service;
|
||||||
|
|
||||||
|
// internal copy, so we can emit with changed index
|
||||||
|
QList<Session*> m_sessions;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FdoSecrets
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
|
@ -20,241 +20,273 @@
|
|||||||
|
|
||||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||||
#include "fdosecrets/FdoSecretsSettings.h"
|
#include "fdosecrets/FdoSecretsSettings.h"
|
||||||
#include "fdosecrets/objects/Collection.h"
|
|
||||||
#include "fdosecrets/objects/Prompt.h"
|
|
||||||
#include "fdosecrets/objects/Session.h"
|
#include "fdosecrets/objects/Session.h"
|
||||||
|
#include "fdosecrets/widgets/SettingsModels.h"
|
||||||
|
|
||||||
#include "core/DatabaseIcons.h"
|
|
||||||
#include "core/FilePath.h"
|
#include "core/FilePath.h"
|
||||||
#include "gui/DatabaseWidget.h"
|
#include "gui/DatabaseWidget.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QDebug>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QPushButton>
|
#include <QItemEditorFactory>
|
||||||
#include <QTableWidget>
|
#include <QStyledItemDelegate>
|
||||||
#include <QToolBar>
|
#include <QToolBar>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
using FdoSecrets::Collection;
|
|
||||||
using FdoSecrets::Service;
|
|
||||||
using FdoSecrets::Session;
|
using FdoSecrets::Session;
|
||||||
|
using FdoSecrets::SettingsDatabaseModel;
|
||||||
|
using FdoSecrets::SettingsSessionModel;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class ManageDatabase : public QToolBar
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(DatabaseWidget* dbWidget READ dbWidget WRITE setDbWidget USER true)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ManageDatabase(FdoSecretsPlugin* plugin, QWidget* parent = nullptr)
|
||||||
|
: QToolBar(parent)
|
||||||
|
, m_plugin(plugin)
|
||||||
|
{
|
||||||
|
setFloatable(false);
|
||||||
|
setMovable(false);
|
||||||
|
|
||||||
|
// use a dummy widget to center the buttons
|
||||||
|
auto spacer = new QWidget(this);
|
||||||
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
spacer->setVisible(true);
|
||||||
|
addWidget(spacer);
|
||||||
|
|
||||||
|
// db settings
|
||||||
|
m_dbSettingsAct = new QAction(tr("Database settings"), this);
|
||||||
|
m_dbSettingsAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("document-edit")));
|
||||||
|
m_dbSettingsAct->setToolTip(tr("Edit database settings"));
|
||||||
|
m_dbSettingsAct->setEnabled(false);
|
||||||
|
connect(m_dbSettingsAct, &QAction::triggered, this, [this]() {
|
||||||
|
if (!m_dbWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto db = m_dbWidget;
|
||||||
|
m_plugin->serviceInstance()->doSwitchToChangeDatabaseSettings(m_dbWidget);
|
||||||
|
});
|
||||||
|
addAction(m_dbSettingsAct);
|
||||||
|
|
||||||
|
// unlock/lock
|
||||||
|
m_lockAct = new QAction(tr("Unlock database"), this);
|
||||||
|
m_lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), false));
|
||||||
|
m_lockAct->setToolTip(tr("Unlock database to show more information"));
|
||||||
|
connect(m_lockAct, &QAction::triggered, this, [this]() {
|
||||||
|
if (!m_dbWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_dbWidget->isLocked()) {
|
||||||
|
m_plugin->serviceInstance()->doUnlockDatabaseInDialog(m_dbWidget);
|
||||||
|
} else {
|
||||||
|
m_dbWidget->lock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addAction(m_lockAct);
|
||||||
|
|
||||||
|
// use a dummy widget to center the buttons
|
||||||
|
spacer = new QWidget(this);
|
||||||
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
spacer->setVisible(true);
|
||||||
|
addWidget(spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseWidget* dbWidget() const
|
||||||
|
{
|
||||||
|
return m_dbWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDbWidget(DatabaseWidget* dbWidget)
|
||||||
|
{
|
||||||
|
if (m_dbWidget == dbWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_dbWidget) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dbWidget = dbWidget;
|
||||||
|
|
||||||
|
reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void disconnect()
|
||||||
|
{
|
||||||
|
if (!m_dbWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_dbWidget->disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reconnect()
|
||||||
|
{
|
||||||
|
if (!m_dbWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connect(m_dbWidget, &DatabaseWidget::databaseLocked, this, [this]() {
|
||||||
|
m_lockAct->setText(tr("Unlock database"));
|
||||||
|
m_lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), false));
|
||||||
|
m_lockAct->setToolTip(tr("Unlock database to show more information"));
|
||||||
|
m_dbSettingsAct->setEnabled(false);
|
||||||
|
});
|
||||||
|
connect(m_dbWidget, &DatabaseWidget::databaseUnlocked, this, [this]() {
|
||||||
|
m_lockAct->setText(tr("Lock database"));
|
||||||
|
m_lockAct->setIcon(
|
||||||
|
filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-unlocked"), false));
|
||||||
|
m_lockAct->setToolTip(tr("Lock database"));
|
||||||
|
m_dbSettingsAct->setEnabled(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FdoSecretsPlugin* m_plugin = nullptr;
|
||||||
|
QPointer<DatabaseWidget> m_dbWidget = nullptr;
|
||||||
|
QAction* m_dbSettingsAct = nullptr;
|
||||||
|
QAction* m_lockAct = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ManageSession : public QToolBar
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(Session* session READ session WRITE setSession USER true)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ManageSession(FdoSecretsPlugin*, QWidget* parent = nullptr)
|
||||||
|
: QToolBar(parent)
|
||||||
|
{
|
||||||
|
setFloatable(false);
|
||||||
|
setMovable(false);
|
||||||
|
|
||||||
|
// use a dummy widget to center the buttons
|
||||||
|
auto spacer = new QWidget(this);
|
||||||
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
spacer->setVisible(true);
|
||||||
|
addWidget(spacer);
|
||||||
|
|
||||||
|
m_disconnectAct = new QAction(tr("Disconnect"), this);
|
||||||
|
m_disconnectAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("dialog-close")));
|
||||||
|
m_disconnectAct->setToolTip(tr("Disconnect this application"));
|
||||||
|
connect(m_disconnectAct, &QAction::triggered, this, [this]() {
|
||||||
|
if (m_session) {
|
||||||
|
m_session->close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addAction(m_disconnectAct);
|
||||||
|
|
||||||
|
// use a dummy widget to center the buttons
|
||||||
|
spacer = new QWidget(this);
|
||||||
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
spacer->setVisible(true);
|
||||||
|
addWidget(spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
Session* session()
|
||||||
|
{
|
||||||
|
return m_session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSession(Session* sess)
|
||||||
|
{
|
||||||
|
m_session = sess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Session* m_session = nullptr;
|
||||||
|
QAction* m_disconnectAct = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> class Creator : public QItemEditorCreatorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline explicit Creator(FdoSecretsPlugin* plugin)
|
||||||
|
: QItemEditorCreatorBase()
|
||||||
|
, m_plugin(plugin)
|
||||||
|
, m_propertyName(T::staticMetaObject.userProperty().name())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QWidget* createWidget(QWidget* parent) const override
|
||||||
|
{
|
||||||
|
return new T(m_plugin, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QByteArray valuePropertyName() const override
|
||||||
|
{
|
||||||
|
return m_propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FdoSecretsPlugin* m_plugin;
|
||||||
|
QByteArray m_propertyName;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent)
|
SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_ui(new Ui::SettingsWidgetFdoSecrets())
|
, m_ui(new Ui::SettingsWidgetFdoSecrets())
|
||||||
|
, m_factory(new QItemEditorFactory)
|
||||||
, m_plugin(plugin)
|
, m_plugin(plugin)
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
auto sessHeader = m_ui->tableSessions->horizontalHeader();
|
auto sessModel = new SettingsSessionModel(plugin, this);
|
||||||
sessHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
m_ui->tableSessions->setModel(sessModel);
|
||||||
sessHeader->setSectionsClickable(false);
|
setupView(m_ui->tableSessions, 1, qMetaTypeId<Session*>(), new Creator<ManageSession>(m_plugin));
|
||||||
sessHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
|
|
||||||
sessHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
|
|
||||||
|
|
||||||
auto dbHeader = m_ui->tableDatabases->horizontalHeader();
|
// config header after setting model, otherwise the header doesn't have enough sections
|
||||||
dbHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
auto sessViewHeader = m_ui->tableSessions->horizontalHeader();
|
||||||
dbHeader->setSectionsClickable(false);
|
sessViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
dbHeader->setSectionResizeMode(0, QHeaderView::Stretch); // file name
|
sessViewHeader->setSectionsClickable(false);
|
||||||
dbHeader->setSectionResizeMode(1, QHeaderView::Stretch); // group
|
sessViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
|
||||||
dbHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); // manage button
|
sessViewHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
|
||||||
|
|
||||||
|
auto dbModel = new SettingsDatabaseModel(plugin->dbTabs(), this);
|
||||||
|
m_ui->tableDatabases->setModel(dbModel);
|
||||||
|
setupView(m_ui->tableDatabases, 2, qMetaTypeId<DatabaseWidget*>(), new Creator<ManageDatabase>(m_plugin));
|
||||||
|
|
||||||
|
// config header after setting model, otherwise the header doesn't have enough sections
|
||||||
|
auto dbViewHeader = m_ui->tableDatabases->horizontalHeader();
|
||||||
|
dbViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
|
dbViewHeader->setSectionsClickable(false);
|
||||||
|
dbViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // file name
|
||||||
|
dbViewHeader->setSectionResizeMode(1, QHeaderView::Stretch); // group
|
||||||
|
dbViewHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); // manage button
|
||||||
|
|
||||||
m_ui->tabWidget->setEnabled(m_ui->enableFdoSecretService->isChecked());
|
m_ui->tabWidget->setEnabled(m_ui->enableFdoSecretService->isChecked());
|
||||||
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, m_ui->tabWidget, &QTabWidget::setEnabled);
|
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, m_ui->tabWidget, &QTabWidget::setEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsWidgetFdoSecrets::setupView(QAbstractItemView* view,
|
||||||
|
int manageColumn,
|
||||||
|
int editorTypeId,
|
||||||
|
QItemEditorCreatorBase* creator)
|
||||||
|
{
|
||||||
|
auto manageButtonDelegate = new QStyledItemDelegate(this);
|
||||||
|
m_factory->registerEditor(editorTypeId, creator);
|
||||||
|
manageButtonDelegate->setItemEditorFactory(m_factory.data());
|
||||||
|
view->setItemDelegateForColumn(manageColumn, manageButtonDelegate);
|
||||||
|
connect(view->model(),
|
||||||
|
&QAbstractItemModel::rowsInserted,
|
||||||
|
this,
|
||||||
|
[view, manageColumn](const QModelIndex&, int first, int last) {
|
||||||
|
for (int i = first; i <= last; ++i) {
|
||||||
|
auto idx = view->model()->index(i, manageColumn);
|
||||||
|
view->openPersistentEditor(idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
SettingsWidgetFdoSecrets::~SettingsWidgetFdoSecrets() = default;
|
SettingsWidgetFdoSecrets::~SettingsWidgetFdoSecrets() = default;
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::populateSessions(bool enabled)
|
|
||||||
{
|
|
||||||
m_ui->tableSessions->setRowCount(0);
|
|
||||||
|
|
||||||
auto service = m_plugin->serviceInstance();
|
|
||||||
if (!service || !enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& sess : service->sessions()) {
|
|
||||||
addSessionRow(sess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::addSessionRow(Session* sess)
|
|
||||||
{
|
|
||||||
auto row = m_ui->tableSessions->rowCount();
|
|
||||||
m_ui->tableSessions->insertRow(row);
|
|
||||||
|
|
||||||
// column 0: application name
|
|
||||||
auto item = new QTableWidgetItem(sess->peer());
|
|
||||||
item->setData(Qt::UserRole, QVariant::fromValue(sess));
|
|
||||||
m_ui->tableSessions->setItem(row, 0, item);
|
|
||||||
|
|
||||||
// column 1: disconnect button
|
|
||||||
auto btn = new QPushButton(tr("Disconnect"));
|
|
||||||
connect(btn, &QPushButton::clicked, sess, &Session::close);
|
|
||||||
m_ui->tableSessions->setCellWidget(row, 1, btn);
|
|
||||||
|
|
||||||
// column 2: hidden uuid
|
|
||||||
m_ui->tableSessions->setItem(row, 2, new QTableWidgetItem(sess->id()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::removeSessionRow(Session* sess)
|
|
||||||
{
|
|
||||||
int row = 0;
|
|
||||||
while (row != m_ui->tableSessions->rowCount()) {
|
|
||||||
auto item = m_ui->tableSessions->item(row, 0);
|
|
||||||
const auto itemSess = item->data(Qt::UserRole).value<Session*>();
|
|
||||||
if (itemSess == sess) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++row;
|
|
||||||
}
|
|
||||||
if (row == m_ui->tableSessions->rowCount()) {
|
|
||||||
qWarning() << "Unknown Fdo Secret Service session" << sess->id() << "while removing collection from table";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ui->tableSessions->removeRow(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::populateDatabases(bool enabled)
|
|
||||||
{
|
|
||||||
m_ui->tableDatabases->setRowCount(0);
|
|
||||||
|
|
||||||
auto service = m_plugin->serviceInstance();
|
|
||||||
if (!service || !enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ret = service->collections();
|
|
||||||
if (ret.isError()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const auto& coll : ret.value()) {
|
|
||||||
addDatabaseRow(coll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::addDatabaseRow(Collection* coll)
|
|
||||||
{
|
|
||||||
auto row = m_ui->tableDatabases->rowCount();
|
|
||||||
m_ui->tableDatabases->insertRow(row);
|
|
||||||
|
|
||||||
// column 0: File name
|
|
||||||
QFileInfo fi(coll->backend()->database()->filePath());
|
|
||||||
auto item = new QTableWidgetItem(fi.fileName());
|
|
||||||
item->setData(Qt::UserRole, QVariant::fromValue(coll));
|
|
||||||
m_ui->tableDatabases->setItem(row, 0, item);
|
|
||||||
|
|
||||||
// column 2: manage button: hboxlayout: unlock/lock settings
|
|
||||||
// create this first so we have a widget to bind connection to,
|
|
||||||
// which can then be auto deleted when the row is deleted.
|
|
||||||
auto widget = createManageButtons(coll);
|
|
||||||
m_ui->tableDatabases->setCellWidget(row, 2, widget);
|
|
||||||
|
|
||||||
// column 1: Group name
|
|
||||||
auto itemGroupName = new QTableWidgetItem();
|
|
||||||
updateExposedGroupItem(itemGroupName, coll);
|
|
||||||
|
|
||||||
connect(coll, &Collection::collectionLockChanged, widget, [this, itemGroupName, coll](bool) {
|
|
||||||
updateExposedGroupItem(itemGroupName, coll);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_ui->tableDatabases->setItem(row, 1, itemGroupName);
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget* SettingsWidgetFdoSecrets::createManageButtons(Collection* coll)
|
|
||||||
{
|
|
||||||
auto toolbar = new QToolBar;
|
|
||||||
toolbar->setFloatable(false);
|
|
||||||
toolbar->setMovable(false);
|
|
||||||
|
|
||||||
// db settings
|
|
||||||
auto dbSettingsAct = new QAction(tr("Database settings"), toolbar);
|
|
||||||
dbSettingsAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("document-edit")));
|
|
||||||
dbSettingsAct->setToolTip(tr("Edit database settings"));
|
|
||||||
dbSettingsAct->setEnabled(!coll->locked().value());
|
|
||||||
connect(dbSettingsAct, &QAction::triggered, this, [this, coll]() {
|
|
||||||
auto db = coll->backend();
|
|
||||||
m_plugin->serviceInstance()->doSwitchToChangeDatabaseSettings(db);
|
|
||||||
});
|
|
||||||
toolbar->addAction(dbSettingsAct);
|
|
||||||
|
|
||||||
// unlock/lock
|
|
||||||
auto lockAct = new QAction(tr("Unlock database"), toolbar);
|
|
||||||
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), true));
|
|
||||||
lockAct->setToolTip(tr("Unlock database to show more information"));
|
|
||||||
connect(coll, &Collection::collectionLockChanged, lockAct, [lockAct, dbSettingsAct](bool locked) {
|
|
||||||
if (locked) {
|
|
||||||
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), true));
|
|
||||||
lockAct->setToolTip(tr("Unlock database to show more information"));
|
|
||||||
} else {
|
|
||||||
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-unlocked"), true));
|
|
||||||
lockAct->setToolTip(tr("Lock database"));
|
|
||||||
}
|
|
||||||
dbSettingsAct->setEnabled(!locked);
|
|
||||||
});
|
|
||||||
connect(lockAct, &QAction::triggered, this, [coll]() {
|
|
||||||
if (coll->locked().value()) {
|
|
||||||
coll->doUnlock();
|
|
||||||
} else {
|
|
||||||
coll->doLock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
toolbar->addAction(lockAct);
|
|
||||||
|
|
||||||
return toolbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::updateExposedGroupItem(QTableWidgetItem* item, Collection* coll)
|
|
||||||
{
|
|
||||||
if (coll->locked().value()) {
|
|
||||||
item->setText(tr("Unlock to show"));
|
|
||||||
item->setIcon(filePath()->icon(QStringLiteral("apps"), QStringLiteral("object-locked"), true));
|
|
||||||
QFont font;
|
|
||||||
font.setItalic(true);
|
|
||||||
item->setFont(font);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto db = coll->backend()->database();
|
|
||||||
auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db));
|
|
||||||
if (group) {
|
|
||||||
item->setText(group->name());
|
|
||||||
item->setIcon(group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
|
|
||||||
: group->iconScaledPixmap());
|
|
||||||
if (group->isExpired()) {
|
|
||||||
QFont font;
|
|
||||||
font.setStrikeOut(true);
|
|
||||||
item->setFont(font);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item->setText(tr("None"));
|
|
||||||
item->setIcon(filePath()->icon(QStringLiteral("apps"), QStringLiteral("paint-none"), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::removeDatabaseRow(Collection* coll)
|
|
||||||
{
|
|
||||||
int row = 0;
|
|
||||||
while (row != m_ui->tableDatabases->rowCount()) {
|
|
||||||
auto item = m_ui->tableDatabases->item(row, 0);
|
|
||||||
const auto itemColl = item->data(Qt::UserRole).value<Collection*>();
|
|
||||||
if (itemColl == coll) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++row;
|
|
||||||
}
|
|
||||||
if (row == m_ui->tableDatabases->rowCount()) {
|
|
||||||
qWarning() << "Unknown Fdo Secret Service collection" << coll->name() << "while removing collection from table";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ui->tableDatabases->removeRow(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::loadSettings()
|
void SettingsWidgetFdoSecrets::loadSettings()
|
||||||
{
|
{
|
||||||
m_ui->enableFdoSecretService->setChecked(FdoSecrets::settings()->isEnabled());
|
m_ui->enableFdoSecretService->setChecked(FdoSecrets::settings()->isEnabled());
|
||||||
@ -269,52 +301,4 @@ void SettingsWidgetFdoSecrets::saveSettings()
|
|||||||
FdoSecrets::settings()->setNoConfirmDeleteItem(m_ui->noConfirmDeleteItem->isChecked());
|
FdoSecrets::settings()->setNoConfirmDeleteItem(m_ui->noConfirmDeleteItem->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::showEvent(QShowEvent* event)
|
#include "SettingsWidgetFdoSecrets.moc"
|
||||||
{
|
|
||||||
QWidget::showEvent(event);
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "updateTables", Qt::QueuedConnection, Q_ARG(bool, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::hideEvent(QHideEvent* event)
|
|
||||||
{
|
|
||||||
QWidget::hideEvent(event);
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "updateTables", Qt::QueuedConnection, Q_ARG(bool, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsWidgetFdoSecrets::updateTables(bool enabled)
|
|
||||||
{
|
|
||||||
if (enabled) {
|
|
||||||
// update the table
|
|
||||||
populateDatabases(m_ui->enableFdoSecretService->isChecked());
|
|
||||||
populateSessions(m_ui->enableFdoSecretService->isChecked());
|
|
||||||
|
|
||||||
// re-layout the widget to adjust the table cell size
|
|
||||||
adjustSize();
|
|
||||||
|
|
||||||
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateSessions);
|
|
||||||
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateDatabases);
|
|
||||||
|
|
||||||
auto service = m_plugin->serviceInstance();
|
|
||||||
if (service) {
|
|
||||||
connect(service, &Service::sessionOpened, this, &SettingsWidgetFdoSecrets::addSessionRow);
|
|
||||||
connect(service, &Service::sessionClosed, this, &SettingsWidgetFdoSecrets::removeSessionRow);
|
|
||||||
connect(service, &Service::collectionCreated, this, &SettingsWidgetFdoSecrets::addDatabaseRow);
|
|
||||||
connect(service, &Service::collectionDeleted, this, &SettingsWidgetFdoSecrets::removeDatabaseRow);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
disconnect(
|
|
||||||
m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateSessions);
|
|
||||||
disconnect(
|
|
||||||
m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateDatabases);
|
|
||||||
|
|
||||||
auto service = m_plugin->serviceInstance();
|
|
||||||
if (service) {
|
|
||||||
disconnect(service, &Service::sessionOpened, this, &SettingsWidgetFdoSecrets::addSessionRow);
|
|
||||||
disconnect(service, &Service::sessionClosed, this, &SettingsWidgetFdoSecrets::removeSessionRow);
|
|
||||||
disconnect(service, &Service::collectionCreated, this, &SettingsWidgetFdoSecrets::addDatabaseRow);
|
|
||||||
disconnect(service, &Service::collectionDeleted, this, &SettingsWidgetFdoSecrets::removeDatabaseRow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,9 @@
|
|||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class QTableWidgetItem;
|
class QAbstractItemView;
|
||||||
|
class QItemEditorCreatorBase;
|
||||||
|
class QItemEditorFactory;
|
||||||
|
|
||||||
namespace FdoSecrets
|
namespace FdoSecrets
|
||||||
{
|
{
|
||||||
@ -48,28 +50,12 @@ public slots:
|
|||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
|
||||||
private slots:
|
|
||||||
void populateSessions(bool enabled);
|
|
||||||
void populateDatabases(bool enabled);
|
|
||||||
void addSessionRow(FdoSecrets::Session* sess);
|
|
||||||
void removeSessionRow(FdoSecrets::Session* sess);
|
|
||||||
void addDatabaseRow(FdoSecrets::Collection* coll);
|
|
||||||
void removeDatabaseRow(FdoSecrets::Collection* coll);
|
|
||||||
|
|
||||||
void updateTables(bool enabled);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void showEvent(QShowEvent* event) override;
|
|
||||||
|
|
||||||
void hideEvent(QHideEvent* event) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* createManageButtons(FdoSecrets::Collection* coll);
|
void setupView(QAbstractItemView* view, int manageColumn, int editorTypeId, QItemEditorCreatorBase* creator);
|
||||||
|
|
||||||
void updateExposedGroupItem(QTableWidgetItem* item, FdoSecrets::Collection* coll);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QScopedPointer<Ui::SettingsWidgetFdoSecrets> m_ui;
|
QScopedPointer<Ui::SettingsWidgetFdoSecrets> m_ui;
|
||||||
|
QScopedPointer<QItemEditorFactory> m_factory;
|
||||||
FdoSecretsPlugin* m_plugin;
|
FdoSecretsPlugin* m_plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableWidget" name="tableDatabases">
|
<widget class="QTableView" name="tableDatabases">
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::NoFocus</enum>
|
<enum>Qt::NoFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -91,21 +91,6 @@
|
|||||||
<attribute name="verticalHeaderVisible">
|
<attribute name="verticalHeaderVisible">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>File Name</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Group</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Manage</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -123,7 +108,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableWidget" name="tableSessions">
|
<widget class="QTableView" name="tableSessions">
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::NoFocus</enum>
|
<enum>Qt::NoFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -139,16 +124,6 @@
|
|||||||
<attribute name="verticalHeaderVisible">
|
<attribute name="verticalHeaderVisible">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Application</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Manage</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -60,7 +60,7 @@ SearchWidget::SearchWidget(QWidget* parent)
|
|||||||
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
|
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
|
||||||
m_ui->searchEdit->installEventFilter(this);
|
m_ui->searchEdit->installEventFilter(this);
|
||||||
|
|
||||||
m_searchMenu = new QMenu();
|
m_searchMenu = new QMenu(this);
|
||||||
m_actionCaseSensitive = m_searchMenu->addAction(tr("Case sensitive"), this, SLOT(updateCaseSensitive()));
|
m_actionCaseSensitive = m_searchMenu->addAction(tr("Case sensitive"), this, SLOT(updateCaseSensitive()));
|
||||||
m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive");
|
m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive");
|
||||||
m_actionCaseSensitive->setCheckable(true);
|
m_actionCaseSensitive->setCheckable(true);
|
||||||
|
59
src/gui/URLEdit.cpp
Normal file
59
src/gui/URLEdit.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||||
|
* Copyright (C) 2019 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 "URLEdit.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "core/Config.h"
|
||||||
|
#include "core/FilePath.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
|
#include "gui/Font.h"
|
||||||
|
|
||||||
|
const QColor URLEdit::ErrorColor = QColor(255, 125, 125);
|
||||||
|
|
||||||
|
URLEdit::URLEdit(QWidget* parent)
|
||||||
|
: QLineEdit(parent)
|
||||||
|
{
|
||||||
|
const QIcon errorIcon = filePath()->icon("status", "dialog-error");
|
||||||
|
m_errorAction = addAction(errorIcon, QLineEdit::TrailingPosition);
|
||||||
|
m_errorAction->setVisible(false);
|
||||||
|
m_errorAction->setToolTip(tr("Invalid URL"));
|
||||||
|
|
||||||
|
updateStylesheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
void URLEdit::enableVerifyMode()
|
||||||
|
{
|
||||||
|
updateStylesheet();
|
||||||
|
|
||||||
|
connect(this, SIGNAL(textChanged(QString)), SLOT(updateStylesheet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void URLEdit::updateStylesheet()
|
||||||
|
{
|
||||||
|
const QString stylesheetTemplate("QLineEdit { background: %1; }");
|
||||||
|
|
||||||
|
if (!Tools::checkUrlValid(text())) {
|
||||||
|
setStyleSheet(stylesheetTemplate.arg(ErrorColor.name()));
|
||||||
|
m_errorAction->setVisible(true);
|
||||||
|
} else {
|
||||||
|
m_errorAction->setVisible(false);
|
||||||
|
setStyleSheet("");
|
||||||
|
}
|
||||||
|
}
|
43
src/gui/URLEdit.h
Normal file
43
src/gui/URLEdit.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||||
|
* Copyright (C) 2019 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 KEEPASSX_URLEDIT_H
|
||||||
|
#define KEEPASSX_URLEDIT_H
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
class URLEdit : public QLineEdit
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const QColor ErrorColor;
|
||||||
|
|
||||||
|
explicit URLEdit(QWidget* parent = nullptr);
|
||||||
|
void enableVerifyMode();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateStylesheet();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<QAction> m_errorAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSX_URLEDIT_H
|
@ -167,6 +167,7 @@ void EditEntryWidget::setupMain()
|
|||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_NETWORKING
|
||||||
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
|
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
|
||||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
||||||
|
m_mainUi->urlEdit->enableVerifyMode();
|
||||||
#endif
|
#endif
|
||||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||||
connect(m_mainUi->notesEnabled, SIGNAL(toggled(bool)), this, SLOT(toggleHideNotes(bool)));
|
connect(m_mainUi->notesEnabled, SIGNAL(toggled(bool)), this, SLOT(toggleHideNotes(bool)));
|
||||||
@ -271,9 +272,14 @@ void EditEntryWidget::setupBrowser()
|
|||||||
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
|
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
|
||||||
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
|
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
|
||||||
|
|
||||||
|
// Use a custom item delegate to align the icon to the right side
|
||||||
|
auto iconDelegate = new URLModelIconDelegate(m_browserUi->additionalURLsView);
|
||||||
|
m_browserUi->additionalURLsView->setItemDelegate(iconDelegate);
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
||||||
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
||||||
|
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
||||||
connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
|
connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
|
||||||
connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
|
connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
|
||||||
connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
|
connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
|
||||||
@ -300,8 +306,10 @@ void EditEntryWidget::updateBrowser()
|
|||||||
|
|
||||||
auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
|
auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
|
||||||
auto hide = m_browserUi->hideEntryCheckbox->isChecked();
|
auto hide = m_browserUi->hideEntryCheckbox->isChecked();
|
||||||
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? QString("true") : QString("false")));
|
auto onlyHttpAuth = m_browserUi->onlyHttpAuthCheckbox->isChecked();
|
||||||
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? QString("true") : QString("false")));
|
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? TRUE_STR : FALSE_STR));
|
||||||
|
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? TRUE_STR : FALSE_STR));
|
||||||
|
m_customData->set(BrowserService::OPTION_ONLY_HTTP_AUTH, (onlyHttpAuth ? TRUE_STR : FALSE_STR));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::insertURL()
|
void EditEntryWidget::insertURL()
|
||||||
@ -465,6 +473,7 @@ void EditEntryWidget::setupEntryUpdate()
|
|||||||
if (config()->get("Browser/Enabled", false).toBool()) {
|
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
|
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
|
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
|
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
@ -959,18 +968,25 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
|
|||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
|
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == "true");
|
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == TRUE_STR);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
} else {
|
} else {
|
||||||
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
|
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_customData->contains(BrowserService::OPTION_HIDE_ENTRY)) {
|
if (m_customData->contains(BrowserService::OPTION_HIDE_ENTRY)) {
|
||||||
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY) == "true");
|
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR);
|
||||||
} else {
|
} else {
|
||||||
m_browserUi->hideEntryCheckbox->setChecked(false);
|
m_browserUi->hideEntryCheckbox->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_customData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)) {
|
||||||
|
m_browserUi->onlyHttpAuthCheckbox->setChecked(m_customData->value(BrowserService::OPTION_ONLY_HTTP_AUTH)
|
||||||
|
== TRUE_STR);
|
||||||
|
} else {
|
||||||
|
m_browserUi->onlyHttpAuthCheckbox->setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
m_browserUi->addURLButton->setEnabled(!m_history);
|
m_browserUi->addURLButton->setEnabled(!m_history);
|
||||||
m_browserUi->removeURLButton->setEnabled(false);
|
m_browserUi->removeURLButton->setEnabled(false);
|
||||||
m_browserUi->editURLButton->setEnabled(false);
|
m_browserUi->editURLButton->setEnabled(false);
|
||||||
|
@ -50,6 +50,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="onlyHttpAuthCheckbox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Only send this setting to the browser for HTTP Auth dialogs. If enabled, normal login forms will not show this entry for selection.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use this entry only with HTTP Basic Auth</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -130,6 +140,7 @@
|
|||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>skipAutoSubmitCheckbox</tabstop>
|
<tabstop>skipAutoSubmitCheckbox</tabstop>
|
||||||
<tabstop>hideEntryCheckbox</tabstop>
|
<tabstop>hideEntryCheckbox</tabstop>
|
||||||
|
<tabstop>onlyHttpAuthCheckbox</tabstop>
|
||||||
<tabstop>additionalURLsView</tabstop>
|
<tabstop>additionalURLsView</tabstop>
|
||||||
<tabstop>addURLButton</tabstop>
|
<tabstop>addURLButton</tabstop>
|
||||||
<tabstop>removeURLButton</tabstop>
|
<tabstop>removeURLButton</tabstop>
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<item row="5" column="1">
|
<item row="5" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="urlEdit">
|
<widget class="URLEdit" name="urlEdit">
|
||||||
<property name="accessibleName">
|
<property name="accessibleName">
|
||||||
<string>Url field</string>
|
<string>Url field</string>
|
||||||
</property>
|
</property>
|
||||||
@ -256,6 +256,12 @@
|
|||||||
<header>gui/PasswordEdit.h</header>
|
<header>gui/PasswordEdit.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>URLEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>gui/URLEdit.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>titleEdit</tabstop>
|
<tabstop>titleEdit</tabstop>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "EntryURLModel.h"
|
#include "EntryURLModel.h"
|
||||||
|
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
|
#include "core/FilePath.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -26,6 +27,7 @@
|
|||||||
EntryURLModel::EntryURLModel(QObject* parent)
|
EntryURLModel::EntryURLModel(QObject* parent)
|
||||||
: QStandardItemModel(parent)
|
: QStandardItemModel(parent)
|
||||||
, m_entryAttributes(nullptr)
|
, m_entryAttributes(nullptr)
|
||||||
|
, m_errorIcon(filePath()->icon("status", "dialog-error"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +55,33 @@ void EntryURLModel::setEntryAttributes(EntryAttributes* entryAttributes)
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = keyByIndex(index);
|
||||||
|
if (key.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto value = m_entryAttributes->value(key);
|
||||||
|
const auto urlValid = Tools::checkUrlValid(value);
|
||||||
|
|
||||||
|
if (role == Qt::BackgroundRole && !urlValid) {
|
||||||
|
return QColor(255, 125, 125);
|
||||||
|
} else if (role == Qt::DecorationRole && !urlValid) {
|
||||||
|
return m_errorIcon;
|
||||||
|
} else if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||||
|
return value;
|
||||||
|
} else if (role == Qt::ToolTipRole && !urlValid) {
|
||||||
|
return tr("Invalid URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool EntryURLModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
bool EntryURLModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
{
|
{
|
||||||
if (!index.isValid() || role != Qt::EditRole || value.type() != QVariant::String || value.toString().isEmpty()) {
|
if (!index.isValid() || role != Qt::EditRole || value.type() != QVariant::String || value.toString().isEmpty()) {
|
||||||
|
@ -20,9 +20,23 @@
|
|||||||
#define KEEPASSXC_ENTRYURLMODEL_H
|
#define KEEPASSXC_ENTRYURLMODEL_H
|
||||||
|
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
class EntryAttributes;
|
class EntryAttributes;
|
||||||
|
|
||||||
|
class URLModelIconDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using QStyledItemDelegate::QStyledItemDelegate;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
|
||||||
|
{
|
||||||
|
QStyledItemDelegate::initStyleOption(option, index);
|
||||||
|
option->decorationPosition = QStyleOptionViewItem::Right;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class EntryURLModel : public QStandardItemModel
|
class EntryURLModel : public QStandardItemModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -32,6 +46,7 @@ public:
|
|||||||
void setEntryAttributes(EntryAttributes* entryAttributes);
|
void setEntryAttributes(EntryAttributes* entryAttributes);
|
||||||
void insertRow(const QString& key, const QString& value);
|
void insertRow(const QString& key, const QString& value);
|
||||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||||
|
QVariant data(const QModelIndex& index, int role) const override;
|
||||||
QModelIndex indexByKey(const QString& key) const;
|
QModelIndex indexByKey(const QString& key) const;
|
||||||
QString keyByIndex(const QModelIndex& index) const;
|
QString keyByIndex(const QModelIndex& index) const;
|
||||||
|
|
||||||
@ -41,6 +56,7 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
QList<QPair<QString, QString>> m_urls;
|
QList<QPair<QString, QString>> m_urls;
|
||||||
EntryAttributes* m_entryAttributes;
|
EntryAttributes* m_entryAttributes;
|
||||||
|
QIcon m_errorIcon;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSXC_ENTRYURLMODEL_H
|
#endif // KEEPASSXC_ENTRYURLMODEL_H
|
||||||
|
@ -20,11 +20,6 @@
|
|||||||
|
|
||||||
#import <AppKit/NSWorkspace.h>
|
#import <AppKit/NSWorkspace.h>
|
||||||
#import <CoreVideo/CVPixelBuffer.h>
|
#import <CoreVideo/CVPixelBuffer.h>
|
||||||
#import <Availability.h>
|
|
||||||
|
|
||||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
|
||||||
static const NSEventMask NSEventMaskKeyDown = NSKeyDownMask;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@implementation AppKitImpl
|
@implementation AppKitImpl
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ endif()
|
|||||||
|
|
||||||
if(WITH_XC_CRYPTO_SSH)
|
if(WITH_XC_CRYPTO_SSH)
|
||||||
add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
|
add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||||
@ -192,7 +192,7 @@ add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp
|
|||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
add_unit_test(NAME testrandomgenerator SOURCES TestRandomGenerator.cpp
|
add_unit_test(NAME testrandomgenerator SOURCES TestRandomGenerator.cpp
|
||||||
LIBS testsupport ${TEST_LIBRARIES})
|
LIBS testsupport ${TEST_LIBRARIES})
|
||||||
|
|
||||||
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp
|
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
@ -206,7 +206,7 @@ add_unit_test(NAME testykchallengeresponsekey
|
|||||||
|
|
||||||
if(WITH_XC_KEESHARE)
|
if(WITH_XC_KEESHARE)
|
||||||
add_unit_test(NAME testsharing SOURCES TestSharing.cpp
|
add_unit_test(NAME testsharing SOURCES TestSharing.cpp
|
||||||
LIBS testsupport ${TEST_LIBRARIES})
|
LIBS testsupport ${TEST_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
|
add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "TestBrowser.h"
|
#include "TestBrowser.h"
|
||||||
#include "TestGlobal.h"
|
#include "TestGlobal.h"
|
||||||
#include "browser/BrowserSettings.h"
|
#include "browser/BrowserSettings.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
#include "sodium/crypto_box.h"
|
#include "sodium/crypto_box.h"
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@ -56,7 +57,7 @@ void TestBrowser::testChangePublicKeys()
|
|||||||
auto response = m_browserAction->handleAction(json);
|
auto response = m_browserAction->handleAction(json);
|
||||||
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
||||||
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
||||||
QCOMPARE(response["success"].toString(), QString("true"));
|
QCOMPARE(response["success"].toString(), TRUE_STR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestBrowser::testEncryptMessage()
|
void TestBrowser::testEncryptMessage()
|
||||||
@ -462,3 +463,22 @@ QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
|
|||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
void TestBrowser::testValidURLs()
|
||||||
|
{
|
||||||
|
QHash<QString, bool> urls;
|
||||||
|
urls["https://github.com/login"] = true;
|
||||||
|
urls["https:///github.com/"] = false;
|
||||||
|
urls["http://github.com/**//*"] = false;
|
||||||
|
urls["http://*.github.com/login"] = false;
|
||||||
|
urls["//github.com"] = true;
|
||||||
|
urls["github.com/{}<>"] = false;
|
||||||
|
urls["http:/example.com"] = false;
|
||||||
|
urls["cmd://C:/Toolchains/msys2/usr/bin/mintty \"ssh jon@192.168.0.1:22\""] = true;
|
||||||
|
urls["file:///Users/testUser/Code/test.html"] = true;
|
||||||
|
|
||||||
|
QHashIterator<QString, bool> i(urls);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
QCOMPARE(Tools::checkUrlValid(i.key()), i.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -47,6 +47,7 @@ private slots:
|
|||||||
void testSubdomainsAndPaths();
|
void testSubdomainsAndPaths();
|
||||||
void testSortEntries();
|
void testSortEntries();
|
||||||
void testGetDatabaseGroups();
|
void testGetDatabaseGroups();
|
||||||
|
void testValidURLs();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
||||||
|
@ -1422,6 +1422,107 @@ void TestCli::testMerge()
|
|||||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestCli::testMergeWithKeys()
|
||||||
|
{
|
||||||
|
Create createCmd;
|
||||||
|
QVERIFY(!createCmd.name.isEmpty());
|
||||||
|
QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name));
|
||||||
|
|
||||||
|
Merge mergeCmd;
|
||||||
|
QVERIFY(!mergeCmd.name.isEmpty());
|
||||||
|
QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name));
|
||||||
|
|
||||||
|
Kdbx4Writer writer;
|
||||||
|
Kdbx4Reader reader;
|
||||||
|
|
||||||
|
QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir());
|
||||||
|
|
||||||
|
QString sourceDatabaseFilename = testDir->path() + "/testSourceDatabase.kdbx";
|
||||||
|
QString sourceKeyfilePath = testDir->path() + "/testSourceKeyfile.txt";
|
||||||
|
|
||||||
|
QString targetDatabaseFilename = testDir->path() + "/testTargetDatabase.kdbx";
|
||||||
|
QString targetKeyfilePath = testDir->path() + "/testTargetKeyfile.txt";
|
||||||
|
|
||||||
|
qint64 pos = m_stdoutFile->pos();
|
||||||
|
|
||||||
|
Utils::Test::setNextPassword("a");
|
||||||
|
createCmd.execute({"create", sourceDatabaseFilename, "-k", sourceKeyfilePath});
|
||||||
|
|
||||||
|
Utils::Test::setNextPassword("b");
|
||||||
|
createCmd.execute({"create", targetDatabaseFilename, "-k", targetKeyfilePath});
|
||||||
|
|
||||||
|
Utils::Test::setNextPassword("a");
|
||||||
|
auto sourceDatabase = QSharedPointer<Database>(
|
||||||
|
Utils::unlockDatabase(sourceDatabaseFilename, true, sourceKeyfilePath, "", Utils::STDOUT));
|
||||||
|
QVERIFY(sourceDatabase);
|
||||||
|
|
||||||
|
Utils::Test::setNextPassword("b");
|
||||||
|
auto targetDatabase = QSharedPointer<Database>(
|
||||||
|
Utils::unlockDatabase(targetDatabaseFilename, true, targetKeyfilePath, "", Utils::STDOUT));
|
||||||
|
QVERIFY(targetDatabase);
|
||||||
|
|
||||||
|
auto* rootGroup = new Group();
|
||||||
|
rootGroup->setName("root");
|
||||||
|
rootGroup->setUuid(QUuid::createUuid());
|
||||||
|
auto* group = new Group();
|
||||||
|
group->setUuid(QUuid::createUuid());
|
||||||
|
group->setParent(rootGroup);
|
||||||
|
group->setName("Internet");
|
||||||
|
|
||||||
|
auto* entry = new Entry();
|
||||||
|
entry->setUuid(QUuid::createUuid());
|
||||||
|
entry->setTitle("Some Website");
|
||||||
|
entry->setPassword("secretsecretsecret");
|
||||||
|
group->addEntry(entry);
|
||||||
|
|
||||||
|
sourceDatabase->setRootGroup(rootGroup);
|
||||||
|
|
||||||
|
auto* otherRootGroup = new Group();
|
||||||
|
otherRootGroup->setName("root");
|
||||||
|
otherRootGroup->setUuid(QUuid::createUuid());
|
||||||
|
auto* otherGroup = new Group();
|
||||||
|
otherGroup->setUuid(QUuid::createUuid());
|
||||||
|
otherGroup->setParent(otherRootGroup);
|
||||||
|
otherGroup->setName("Internet");
|
||||||
|
|
||||||
|
auto* otherEntry = new Entry();
|
||||||
|
otherEntry->setUuid(QUuid::createUuid());
|
||||||
|
otherEntry->setTitle("Some Website 2");
|
||||||
|
otherEntry->setPassword("secretsecretsecret 2");
|
||||||
|
otherGroup->addEntry(otherEntry);
|
||||||
|
|
||||||
|
targetDatabase->setRootGroup(otherRootGroup);
|
||||||
|
|
||||||
|
QFile sourceDatabaseFile(sourceDatabaseFilename);
|
||||||
|
sourceDatabaseFile.open(QIODevice::WriteOnly);
|
||||||
|
QVERIFY(writer.writeDatabase(&sourceDatabaseFile, sourceDatabase.data()));
|
||||||
|
sourceDatabaseFile.flush();
|
||||||
|
sourceDatabaseFile.close();
|
||||||
|
|
||||||
|
QFile targetDatabaseFile(targetDatabaseFilename);
|
||||||
|
targetDatabaseFile.open(QIODevice::WriteOnly);
|
||||||
|
QVERIFY(writer.writeDatabase(&targetDatabaseFile, targetDatabase.data()));
|
||||||
|
targetDatabaseFile.flush();
|
||||||
|
targetDatabaseFile.close();
|
||||||
|
|
||||||
|
pos = m_stdoutFile->pos();
|
||||||
|
Utils::Test::setNextPassword("b");
|
||||||
|
Utils::Test::setNextPassword("a");
|
||||||
|
mergeCmd.execute({"merge",
|
||||||
|
"-k",
|
||||||
|
targetKeyfilePath,
|
||||||
|
"--key-file-from",
|
||||||
|
sourceKeyfilePath,
|
||||||
|
targetDatabaseFile.fileName(),
|
||||||
|
sourceDatabaseFile.fileName()});
|
||||||
|
|
||||||
|
m_stdoutFile->seek(pos);
|
||||||
|
QList<QByteArray> lines = m_stdoutFile->readAll().split('\n');
|
||||||
|
QVERIFY(lines.contains(QString("Successfully merged %1 into %2.")
|
||||||
|
.arg(sourceDatabaseFile.fileName(), targetDatabaseFile.fileName())
|
||||||
|
.toUtf8()));
|
||||||
|
}
|
||||||
|
|
||||||
void TestCli::testMove()
|
void TestCli::testMove()
|
||||||
{
|
{
|
||||||
Move moveCmd;
|
Move moveCmd;
|
||||||
|
@ -66,6 +66,7 @@ private slots:
|
|||||||
void testList();
|
void testList();
|
||||||
void testLocate();
|
void testLocate();
|
||||||
void testMerge();
|
void testMerge();
|
||||||
|
void testMergeWithKeys();
|
||||||
void testMove();
|
void testMove();
|
||||||
void testOpen();
|
void testOpen();
|
||||||
void testRemove();
|
void testRemove();
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "TestGlobal.h"
|
#include "TestGlobal.h"
|
||||||
#include "mock/MockClock.h"
|
#include "mock/MockClock.h"
|
||||||
|
|
||||||
|
#include <QScopedPointer>
|
||||||
#include <QSignalSpy>
|
#include <QSignalSpy>
|
||||||
|
|
||||||
#include "core/Metadata.h"
|
#include "core/Metadata.h"
|
||||||
@ -798,16 +799,16 @@ void TestGroup::testAddEntryWithPath()
|
|||||||
|
|
||||||
void TestGroup::testIsRecycled()
|
void TestGroup::testIsRecycled()
|
||||||
{
|
{
|
||||||
Database* db = new Database();
|
Database db;
|
||||||
db->metadata()->setRecycleBinEnabled(true);
|
db.metadata()->setRecycleBinEnabled(true);
|
||||||
|
|
||||||
Group* group1 = new Group();
|
Group* group1 = new Group();
|
||||||
group1->setName("group1");
|
group1->setName("group1");
|
||||||
group1->setParent(db->rootGroup());
|
group1->setParent(db.rootGroup());
|
||||||
|
|
||||||
Group* group2 = new Group();
|
Group* group2 = new Group();
|
||||||
group2->setName("group2");
|
group2->setName("group2");
|
||||||
group2->setParent(db->rootGroup());
|
group2->setParent(db.rootGroup());
|
||||||
|
|
||||||
Group* group3 = new Group();
|
Group* group3 = new Group();
|
||||||
group3->setName("group3");
|
group3->setName("group3");
|
||||||
@ -815,16 +816,16 @@ void TestGroup::testIsRecycled()
|
|||||||
|
|
||||||
Group* group4 = new Group();
|
Group* group4 = new Group();
|
||||||
group4->setName("group4");
|
group4->setName("group4");
|
||||||
group4->setParent(db->rootGroup());
|
group4->setParent(db.rootGroup());
|
||||||
|
|
||||||
db->recycleGroup(group2);
|
db.recycleGroup(group2);
|
||||||
|
|
||||||
QVERIFY(!group1->isRecycled());
|
QVERIFY(!group1->isRecycled());
|
||||||
QVERIFY(group2->isRecycled());
|
QVERIFY(group2->isRecycled());
|
||||||
QVERIFY(group3->isRecycled());
|
QVERIFY(group3->isRecycled());
|
||||||
QVERIFY(!group4->isRecycled());
|
QVERIFY(!group4->isRecycled());
|
||||||
|
|
||||||
db->recycleGroup(group4);
|
db.recycleGroup(group4);
|
||||||
QVERIFY(group4->isRecycled());
|
QVERIFY(group4->isRecycled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1052,12 +1053,12 @@ void TestGroup::testChildrenSort()
|
|||||||
|
|
||||||
void TestGroup::testHierarchy()
|
void TestGroup::testHierarchy()
|
||||||
{
|
{
|
||||||
Group* group1 = new Group();
|
Group group1;
|
||||||
group1->setName("group1");
|
group1.setName("group1");
|
||||||
|
|
||||||
Group* group2 = new Group();
|
Group* group2 = new Group();
|
||||||
group2->setName("group2");
|
group2->setName("group2");
|
||||||
group2->setParent(group1);
|
group2->setParent(&group1);
|
||||||
|
|
||||||
Group* group3 = new Group();
|
Group* group3 = new Group();
|
||||||
group3->setName("group3");
|
group3->setName("group3");
|
||||||
@ -1085,11 +1086,11 @@ void TestGroup::testHierarchy()
|
|||||||
void TestGroup::testApplyGroupIconRecursively()
|
void TestGroup::testApplyGroupIconRecursively()
|
||||||
{
|
{
|
||||||
// Create a database with two nested groups with one entry each
|
// Create a database with two nested groups with one entry each
|
||||||
Database* database = new Database();
|
Database database;
|
||||||
|
|
||||||
Group* subgroup = new Group();
|
Group* subgroup = new Group();
|
||||||
subgroup->setName("Subgroup");
|
subgroup->setName("Subgroup");
|
||||||
subgroup->setParent(database->rootGroup());
|
subgroup->setParent(database.rootGroup());
|
||||||
QVERIFY(subgroup);
|
QVERIFY(subgroup);
|
||||||
|
|
||||||
Group* subsubgroup = new Group();
|
Group* subsubgroup = new Group();
|
||||||
@ -1108,10 +1109,10 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
// Set an icon per number to the root group and apply recursively
|
// Set an icon per number to the root group and apply recursively
|
||||||
// -> all groups and entries have the same icon
|
// -> all groups and entries have the same icon
|
||||||
const int rootIconNumber = 42;
|
const int rootIconNumber = 42;
|
||||||
database->rootGroup()->setIcon(rootIconNumber);
|
database.rootGroup()->setIcon(rootIconNumber);
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
|
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
|
||||||
database->rootGroup()->applyGroupIconToChildGroups();
|
database.rootGroup()->applyGroupIconToChildGroups();
|
||||||
database->rootGroup()->applyGroupIconToChildEntries();
|
database.rootGroup()->applyGroupIconToChildEntries();
|
||||||
QVERIFY(subgroup->iconNumber() == rootIconNumber);
|
QVERIFY(subgroup->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subsubgroup->iconNumber() == rootIconNumber);
|
QVERIFY(subsubgroup->iconNumber() == rootIconNumber);
|
||||||
@ -1124,7 +1125,7 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber);
|
QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber);
|
||||||
subsubgroup->applyGroupIconToChildGroups();
|
subsubgroup->applyGroupIconToChildGroups();
|
||||||
subsubgroup->applyGroupIconToChildEntries();
|
subsubgroup->applyGroupIconToChildEntries();
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
|
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subgroup->iconNumber() == rootIconNumber);
|
QVERIFY(subgroup->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber);
|
QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber);
|
||||||
@ -1135,11 +1136,11 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
const QUuid subgroupIconUuid = QUuid::createUuid();
|
const QUuid subgroupIconUuid = QUuid::createUuid();
|
||||||
QImage subgroupIcon(16, 16, QImage::Format_RGB32);
|
QImage subgroupIcon(16, 16, QImage::Format_RGB32);
|
||||||
subgroupIcon.setPixel(0, 0, qRgb(255, 0, 0));
|
subgroupIcon.setPixel(0, 0, qRgb(255, 0, 0));
|
||||||
database->metadata()->addCustomIcon(subgroupIconUuid, subgroupIcon);
|
database.metadata()->addCustomIcon(subgroupIconUuid, subgroupIcon);
|
||||||
subgroup->setIcon(subgroupIconUuid);
|
subgroup->setIcon(subgroupIconUuid);
|
||||||
subgroup->applyGroupIconToChildGroups();
|
subgroup->applyGroupIconToChildGroups();
|
||||||
subgroup->applyGroupIconToChildEntries();
|
subgroup->applyGroupIconToChildEntries();
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
|
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
|
||||||
QCOMPARE(subgroup->iconUuid(), subgroupIconUuid);
|
QCOMPARE(subgroup->iconUuid(), subgroupIconUuid);
|
||||||
QCOMPARE(subgroup->icon(), subgroupIcon);
|
QCOMPARE(subgroup->icon(), subgroupIcon);
|
||||||
QCOMPARE(subgroupEntry->iconUuid(), subgroupIconUuid);
|
QCOMPARE(subgroupEntry->iconUuid(), subgroupIconUuid);
|
||||||
@ -1150,10 +1151,10 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
QCOMPARE(subsubgroupEntry->icon(), subgroupIcon);
|
QCOMPARE(subsubgroupEntry->icon(), subgroupIcon);
|
||||||
|
|
||||||
// Reset all icons to root icon
|
// Reset all icons to root icon
|
||||||
database->rootGroup()->setIcon(rootIconNumber);
|
database.rootGroup()->setIcon(rootIconNumber);
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
|
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
|
||||||
database->rootGroup()->applyGroupIconToChildGroups();
|
database.rootGroup()->applyGroupIconToChildGroups();
|
||||||
database->rootGroup()->applyGroupIconToChildEntries();
|
database.rootGroup()->applyGroupIconToChildEntries();
|
||||||
QVERIFY(subgroup->iconNumber() == rootIconNumber);
|
QVERIFY(subgroup->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subsubgroup->iconNumber() == rootIconNumber);
|
QVERIFY(subsubgroup->iconNumber() == rootIconNumber);
|
||||||
@ -1161,10 +1162,10 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
|
|
||||||
// Apply only for child groups
|
// Apply only for child groups
|
||||||
const int iconForGroups = 10;
|
const int iconForGroups = 10;
|
||||||
database->rootGroup()->setIcon(iconForGroups);
|
database.rootGroup()->setIcon(iconForGroups);
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == iconForGroups);
|
QVERIFY(database.rootGroup()->iconNumber() == iconForGroups);
|
||||||
database->rootGroup()->applyGroupIconToChildGroups();
|
database.rootGroup()->applyGroupIconToChildGroups();
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == iconForGroups);
|
QVERIFY(database.rootGroup()->iconNumber() == iconForGroups);
|
||||||
QVERIFY(subgroup->iconNumber() == iconForGroups);
|
QVERIFY(subgroup->iconNumber() == iconForGroups);
|
||||||
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
|
||||||
QVERIFY(subsubgroup->iconNumber() == iconForGroups);
|
QVERIFY(subsubgroup->iconNumber() == iconForGroups);
|
||||||
@ -1172,10 +1173,10 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
|
|
||||||
// Apply only for child entries
|
// Apply only for child entries
|
||||||
const int iconForEntries = 20;
|
const int iconForEntries = 20;
|
||||||
database->rootGroup()->setIcon(iconForEntries);
|
database.rootGroup()->setIcon(iconForEntries);
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == iconForEntries);
|
QVERIFY(database.rootGroup()->iconNumber() == iconForEntries);
|
||||||
database->rootGroup()->applyGroupIconToChildEntries();
|
database.rootGroup()->applyGroupIconToChildEntries();
|
||||||
QVERIFY(database->rootGroup()->iconNumber() == iconForEntries);
|
QVERIFY(database.rootGroup()->iconNumber() == iconForEntries);
|
||||||
QVERIFY(subgroup->iconNumber() == iconForGroups);
|
QVERIFY(subgroup->iconNumber() == iconForGroups);
|
||||||
QVERIFY(subgroupEntry->iconNumber() == iconForEntries);
|
QVERIFY(subgroupEntry->iconNumber() == iconForEntries);
|
||||||
QVERIFY(subsubgroup->iconNumber() == iconForGroups);
|
QVERIFY(subsubgroup->iconNumber() == iconForGroups);
|
||||||
@ -1184,15 +1185,15 @@ void TestGroup::testApplyGroupIconRecursively()
|
|||||||
|
|
||||||
void TestGroup::testUsernamesRecursive()
|
void TestGroup::testUsernamesRecursive()
|
||||||
{
|
{
|
||||||
Database* database = new Database();
|
Database database;
|
||||||
|
|
||||||
// Create a subgroup
|
// Create a subgroup
|
||||||
Group* subgroup = new Group();
|
Group* subgroup = new Group();
|
||||||
subgroup->setName("Subgroup");
|
subgroup->setName("Subgroup");
|
||||||
subgroup->setParent(database->rootGroup());
|
subgroup->setParent(database.rootGroup());
|
||||||
|
|
||||||
// Generate entries in the root group and the subgroup
|
// Generate entries in the root group and the subgroup
|
||||||
Entry* rootGroupEntry = database->rootGroup()->addEntryWithPath("Root group entry");
|
Entry* rootGroupEntry = database.rootGroup()->addEntryWithPath("Root group entry");
|
||||||
rootGroupEntry->setUsername("Name1");
|
rootGroupEntry->setUsername("Name1");
|
||||||
|
|
||||||
Entry* subgroupEntry = subgroup->addEntryWithPath("Subgroup entry");
|
Entry* subgroupEntry = subgroup->addEntryWithPath("Subgroup entry");
|
||||||
@ -1201,7 +1202,7 @@ void TestGroup::testUsernamesRecursive()
|
|||||||
Entry* subgroupEntryReusingUsername = subgroup->addEntryWithPath("Another subgroup entry");
|
Entry* subgroupEntryReusingUsername = subgroup->addEntryWithPath("Another subgroup entry");
|
||||||
subgroupEntryReusingUsername->setUsername("Name2");
|
subgroupEntryReusingUsername->setUsername("Name2");
|
||||||
|
|
||||||
QList<QString> usernames = database->rootGroup()->usernamesRecursive();
|
QList<QString> usernames = database.rootGroup()->usernamesRecursive();
|
||||||
QCOMPARE(usernames.size(), 2);
|
QCOMPARE(usernames.size(), 2);
|
||||||
QVERIFY(usernames.contains("Name1"));
|
QVERIFY(usernames.contains("Name1"));
|
||||||
QVERIFY(usernames.contains("Name2"));
|
QVERIFY(usernames.contains("Name2"));
|
||||||
|
@ -49,24 +49,24 @@ QPair<QString, QString>* split1PTextExportKV(QByteArray& line)
|
|||||||
return new QPair<QString, QString>(k, v);
|
return new QPair<QString, QString>(k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray* read1PasswordTextExport(QFile& f)
|
QSharedPointer<QJsonArray> read1PasswordTextExport(QFile& f)
|
||||||
{
|
{
|
||||||
auto result = new QJsonArray;
|
|
||||||
auto current = new QJsonObject;
|
|
||||||
|
|
||||||
if (!f.open(QIODevice::ReadOnly)) {
|
if (!f.open(QIODevice::ReadOnly)) {
|
||||||
qCritical("Unable to open your text export file for reading");
|
qCritical("Unable to open your text export file for reading");
|
||||||
return nullptr;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto result = QSharedPointer<QJsonArray>::create();
|
||||||
|
QJsonObject current;
|
||||||
|
|
||||||
while (!f.atEnd()) {
|
while (!f.atEnd()) {
|
||||||
auto line = f.readLine(1024);
|
auto line = f.readLine(1024);
|
||||||
|
|
||||||
if (line.size() == 1 and line[0] == '\n') {
|
if (line.size() == 1 and line[0] == '\n') {
|
||||||
if (!current->isEmpty()) {
|
if (!current.isEmpty()) {
|
||||||
result->append(*current);
|
result->append(current);
|
||||||
}
|
}
|
||||||
current = new QJsonObject;
|
current = QJsonObject();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto kv = split1PTextExportKV(line);
|
const auto kv = split1PTextExportKV(line);
|
||||||
@ -95,14 +95,14 @@ QJsonArray* read1PasswordTextExport(QFile& f)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto v = lines.join("");
|
auto v = lines.join("");
|
||||||
(*current)[k] = v;
|
current[k] = v;
|
||||||
} else {
|
} else {
|
||||||
(*current)[k] = kv->second;
|
current[k] = kv->second;
|
||||||
}
|
}
|
||||||
delete kv;
|
delete kv;
|
||||||
}
|
}
|
||||||
if (!current->isEmpty()) {
|
if (!current.isEmpty()) {
|
||||||
result->append(*current);
|
result->append(current);
|
||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
@ -120,10 +120,9 @@ void TestOpVaultReader::initTestCase()
|
|||||||
m_password = "freddy";
|
m_password = "freddy";
|
||||||
|
|
||||||
QFile testData(m_opVaultTextExportPath);
|
QFile testData(m_opVaultTextExportPath);
|
||||||
QJsonArray* data = read1PasswordTextExport(testData);
|
auto data = read1PasswordTextExport(testData);
|
||||||
QVERIFY(data);
|
QVERIFY(data);
|
||||||
QCOMPARE(data->size(), 27);
|
QCOMPARE(data->size(), 27);
|
||||||
delete data;
|
|
||||||
|
|
||||||
m_categoryMap.insert("001", "Login");
|
m_categoryMap.insert("001", "Login");
|
||||||
m_categoryMap.insert("002", "Credit Card");
|
m_categoryMap.insert("002", "Credit Card");
|
||||||
@ -149,9 +148,9 @@ void TestOpVaultReader::testReadIntoDatabase()
|
|||||||
{
|
{
|
||||||
QDir opVaultDir(m_opVaultPath);
|
QDir opVaultDir(m_opVaultPath);
|
||||||
|
|
||||||
auto reader = new OpVaultReader();
|
OpVaultReader reader;
|
||||||
auto db = reader->readDatabase(opVaultDir, m_password);
|
QScopedPointer<Database> db(reader.readDatabase(opVaultDir, m_password));
|
||||||
QVERIFY2(!reader->hasError(), qPrintable(reader->errorString()));
|
QVERIFY2(!reader.hasError(), qPrintable(reader.errorString()));
|
||||||
QVERIFY(db);
|
QVERIFY(db);
|
||||||
QVERIFY(!db->children().isEmpty());
|
QVERIFY(!db->children().isEmpty());
|
||||||
|
|
||||||
@ -179,7 +178,6 @@ void TestOpVaultReader::testReadIntoDatabase()
|
|||||||
QUuid u = Tools::hexToUuid(value["uuid"].toString());
|
QUuid u = Tools::hexToUuid(value["uuid"].toString());
|
||||||
objectsByUuid[u] = value;
|
objectsByUuid[u] = value;
|
||||||
}
|
}
|
||||||
delete testData;
|
|
||||||
QCOMPARE(objectsByUuid.size(), 27);
|
QCOMPARE(objectsByUuid.size(), 27);
|
||||||
|
|
||||||
for (QUuid u : objectsByUuid.keys()) {
|
for (QUuid u : objectsByUuid.keys()) {
|
||||||
@ -240,11 +238,11 @@ void TestOpVaultReader::testKeyDerivation()
|
|||||||
|
|
||||||
void TestOpVaultReader::testBandEntry1()
|
void TestOpVaultReader::testBandEntry1()
|
||||||
{
|
{
|
||||||
auto reader = new OpVaultReader();
|
OpVaultReader reader;
|
||||||
QByteArray json(R"({"hello": "world"})");
|
QByteArray json(R"({"hello": "world"})");
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(json);
|
QJsonDocument doc = QJsonDocument::fromJson(json);
|
||||||
QJsonObject data;
|
QJsonObject data;
|
||||||
QByteArray entryKey;
|
QByteArray entryKey;
|
||||||
QByteArray entryHmacKey;
|
QByteArray entryHmacKey;
|
||||||
QVERIFY(!reader->decryptBandEntry(doc.object(), data, entryKey, entryHmacKey));
|
QVERIFY(!reader.decryptBandEntry(doc.object(), data, entryKey, entryHmacKey));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user