Merge branch 'release/2.5.2' into develop

This commit is contained in:
Jonathan White 2019-12-27 19:38:44 -05:00
commit 2fab4d576a
41 changed files with 1265 additions and 406 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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"

View 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

View 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

View File

@ -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

View File

@ -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;
} }

View File

@ -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)) {

View File

@ -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:

View File

@ -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"));

View File

@ -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)

View File

@ -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;

View File

@ -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");

View File

@ -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,

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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()) {

View File

@ -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()) {

View File

@ -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(),

View 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

View 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

View File

@ -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);
}
}
}

View File

@ -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;
}; };

View File

@ -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>

View File

@ -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
View 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
View 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

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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()) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
@ -461,4 +462,23 @@ 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());
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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"));

View File

@ -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));
} }