mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Add support for multiple URLs in an entry
* Fixes #398 The new Browser Integration entry settings page has a list view with any additional URL's. These URL's are added to the entry attributes with KP2A_URL_<counter>, which means those are directly compatible with Keepass2Android.
This commit is contained in:
parent
e50261a99c
commit
f726d7501f
@ -218,6 +218,7 @@ add_subdirectory(proxy)
|
|||||||
if(WITH_XC_BROWSER)
|
if(WITH_XC_BROWSER)
|
||||||
set(keepassxcbrowser_LIB keepassxcbrowser)
|
set(keepassxcbrowser_LIB keepassxcbrowser)
|
||||||
set(keepassx_SOURCES ${keepassx_SOURCES} gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp)
|
set(keepassx_SOURCES ${keepassx_SOURCES} gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp)
|
||||||
|
set(keepassx_SOURCES ${keepassx_SOURCES} gui/entry/EntryURLModel.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(autotype)
|
add_subdirectory(autotype)
|
||||||
|
@ -53,12 +53,6 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
|||||||
|
|
||||||
m_ui->scriptWarningWidget->setVisible(false);
|
m_ui->scriptWarningWidget->setVisible(false);
|
||||||
m_ui->scriptWarningWidget->setAutoHideTimeout(-1);
|
m_ui->scriptWarningWidget->setAutoHideTimeout(-1);
|
||||||
m_ui->scriptWarningWidget->showMessage(
|
|
||||||
tr("<b>Warning</b>, the keepassxc-proxy application was not found!"
|
|
||||||
"<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options."
|
|
||||||
"<br />Browser integration WILL NOT WORK without the proxy application."
|
|
||||||
"<br />Expected Path: "),
|
|
||||||
MessageWidget::Warning);
|
|
||||||
|
|
||||||
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
|
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
|
||||||
MessageWidget::Warning);
|
MessageWidget::Warning);
|
||||||
@ -154,9 +148,13 @@ void BrowserOptionDialog::loadSettings()
|
|||||||
// Check for native messaging host location errors
|
// Check for native messaging host location errors
|
||||||
QString path;
|
QString path;
|
||||||
if (!settings->checkIfProxyExists(path)) {
|
if (!settings->checkIfProxyExists(path)) {
|
||||||
QString text = m_ui->scriptWarningWidget->text();
|
auto text =
|
||||||
text.append(path);
|
tr("<b>Warning</b>, the keepassxc-proxy application was not found!"
|
||||||
m_ui->scriptWarningWidget->setText(text);
|
"<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options."
|
||||||
|
"<br />Browser integration WILL NOT WORK without the proxy application."
|
||||||
|
"<br />Expected Path: %1")
|
||||||
|
.arg(path);
|
||||||
|
m_ui->scriptWarningWidget->showMessage(text, MessageWidget::Warning);
|
||||||
m_ui->scriptWarningWidget->setVisible(true);
|
m_ui->scriptWarningWidget->setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
m_ui->scriptWarningWidget->setVisible(false);
|
m_ui->scriptWarningWidget->setVisible(false);
|
||||||
|
@ -41,18 +41,20 @@
|
|||||||
#include "gui/macutils/MacUtils.h"
|
#include "gui/macutils/MacUtils.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char BrowserService::KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
|
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||||
const char BrowserService::KEEPASSXCBROWSER_OLD_NAME[] = "keepassxc-browser Settings";
|
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
||||||
const char BrowserService::ASSOCIATE_KEY_PREFIX[] = "KPXC_BROWSER_";
|
const QString BrowserService::ASSOCIATE_KEY_PREFIX = QStringLiteral("KPXC_BROWSER_");
|
||||||
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
|
static const QString KEEPASSXCBROWSER_GROUP_NAME = QStringLiteral("KeePassXC-Browser Passwords");
|
||||||
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
||||||
// These are for the settings and password conversion
|
// These are for the settings and password conversion
|
||||||
const char BrowserService::LEGACY_ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
|
const QString BrowserService::LEGACY_ASSOCIATE_KEY_PREFIX = QStringLiteral("Public Key: ");
|
||||||
static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings";
|
static const QString KEEPASSHTTP_NAME = QStringLiteral("KeePassHttp Settings");
|
||||||
static const char KEEPASSHTTP_GROUP_NAME[] = "KeePassHttp Passwords";
|
static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwords");
|
||||||
// Extra entry related options saved in custom data
|
// Extra entry related options saved in custom data
|
||||||
const char BrowserService::OPTION_SKIP_AUTO_SUBMIT[] = "BrowserSkipAutoSubmit";
|
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
|
||||||
const char BrowserService::OPTION_HIDE_ENTRY[] = "BrowserHideEntry";
|
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
|
||||||
|
// Multiple URL's
|
||||||
|
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
|
||||||
|
|
||||||
BrowserService::BrowserService(DatabaseTabWidget* parent)
|
BrowserService::BrowserService(DatabaseTabWidget* parent)
|
||||||
: m_dbTabWidget(parent)
|
: m_dbTabWidget(parent)
|
||||||
@ -320,7 +322,7 @@ QString BrowserService::storeKey(const QString& key)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
contains = db->metadata()->customData()->contains(ASSOCIATE_KEY_PREFIX + id);
|
||||||
if (contains) {
|
if (contains) {
|
||||||
dialogResult = MessageBox::warning(nullptr,
|
dialogResult = MessageBox::warning(nullptr,
|
||||||
tr("KeePassXC: Overwrite existing key?"),
|
tr("KeePassXC: Overwrite existing key?"),
|
||||||
@ -333,7 +335,7 @@ QString BrowserService::storeKey(const QString& key)
|
|||||||
} while (contains && dialogResult == MessageBox::Cancel);
|
} while (contains && dialogResult == MessageBox::Cancel);
|
||||||
|
|
||||||
hideWindow();
|
hideWindow();
|
||||||
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key);
|
db->metadata()->customData()->set(ASSOCIATE_KEY_PREFIX + id, key);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +346,7 @@ QString BrowserService::getKey(const QString& id)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
return db->metadata()->customData()->value(ASSOCIATE_KEY_PREFIX + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||||
@ -377,9 +379,9 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
|||||||
// Check entries for authorization
|
// Check entries for authorization
|
||||||
QList<Entry*> pwEntriesToConfirm;
|
QList<Entry*> pwEntriesToConfirm;
|
||||||
QList<Entry*> pwEntries;
|
QList<Entry*> pwEntries;
|
||||||
for (Entry* entry : searchEntries(url, keyList)) {
|
for (auto* entry : searchEntries(url, 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") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,7 +427,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
|||||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||||
|
|
||||||
// Fill the list
|
// Fill the list
|
||||||
for (Entry* entry : pwEntries) {
|
for (auto* entry : pwEntries) {
|
||||||
result.append(prepareEntry(entry));
|
result.append(prepareEntry(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,22 +590,30 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Entry* entry : EntrySearcher().search(baseDomain(hostname), rootGroup)) {
|
for (const auto& group : rootGroup->groupsRecursive(true)) {
|
||||||
QString entryUrl = entry->url();
|
if (group->isRecycled() || !group->resolveSearchingEnabled()) {
|
||||||
QUrl entryQUrl(entryUrl);
|
|
||||||
QString entryScheme = entryQUrl.scheme();
|
|
||||||
QUrl qUrl(url);
|
|
||||||
|
|
||||||
// Ignore entry if port or scheme defined in the URL doesn't match
|
|
||||||
if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port())
|
|
||||||
|| (browserSettings()->matchUrlScheme() && !entryScheme.isEmpty()
|
|
||||||
&& entryScheme.compare(qUrl.scheme()) != 0)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter to match hostname in URL field
|
for (auto* entry : group->entries()) {
|
||||||
if ((!entryUrl.isEmpty() && hostname.contains(entryUrl))
|
if (entry->isRecycled()) {
|
||||||
|| (matchUrlScheme(entryUrl) && hostname.endsWith(entryQUrl.host()))) {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for additional URL's starting with KP2A_URL
|
||||||
|
if (entry->attributes()->keys().contains(ADDITIONAL_URL)) {
|
||||||
|
for (const auto& key : entry->attributes()->keys()) {
|
||||||
|
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), hostname, url)) {
|
||||||
|
entries.append(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handleURL(entry->url(), hostname, url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
entries.append(entry);
|
entries.append(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,7 +626,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
|
|||||||
// Check if database is connected with KeePassXC-Browser
|
// Check if database is connected with KeePassXC-Browser
|
||||||
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
||||||
for (const StringPair& keyPair : keyList) {
|
for (const StringPair& keyPair : keyList) {
|
||||||
QString key = db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
|
QString key = db->metadata()->customData()->value(ASSOCIATE_KEY_PREFIX + keyPair.first);
|
||||||
if (!key.isEmpty() && keyPair.second == key) {
|
if (!key.isEmpty() && keyPair.second == key) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -668,7 +678,7 @@ void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database
|
|||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
int keyCounter = 0;
|
int keyCounter = 0;
|
||||||
for (Entry* entry : entries) {
|
for (auto* entry : entries) {
|
||||||
if (progress.wasCanceled()) {
|
if (progress.wasCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -721,12 +731,9 @@ void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString keePassBrowserGroupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
|
for (auto* g : rootGroup->groupsRecursive(true)) {
|
||||||
const QString keePassHttpGroupName = QLatin1String(KEEPASSHTTP_GROUP_NAME);
|
if (g->name() == KEEPASSHTTP_GROUP_NAME) {
|
||||||
|
g->setName(KEEPASSXCBROWSER_GROUP_NAME);
|
||||||
for (Group* g : rootGroup->groupsRecursive(true)) {
|
|
||||||
if (g->name() == keePassHttpGroupName) {
|
|
||||||
g->setName(keePassBrowserGroupName);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -745,7 +752,7 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
|
|||||||
|
|
||||||
// Build map of prioritized entries
|
// Build map of prioritized entries
|
||||||
QMultiMap<int, Entry*> priorities;
|
QMultiMap<int, Entry*> priorities;
|
||||||
for (Entry* entry : pwEntries) {
|
for (auto* entry : pwEntries) {
|
||||||
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
|
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,7 +808,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
|||||||
|
|
||||||
int res = accessControlDialog.exec();
|
int res = accessControlDialog.exec();
|
||||||
if (accessControlDialog.remember()) {
|
if (accessControlDialog.remember()) {
|
||||||
for (Entry* entry : pwEntriesToConfirm) {
|
for (auto* entry : pwEntriesToConfirm) {
|
||||||
BrowserEntryConfig config;
|
BrowserEntryConfig config;
|
||||||
config.load(entry);
|
config.load(entry);
|
||||||
if (res == QDialog::Accepted) {
|
if (res == QDialog::Accepted) {
|
||||||
@ -853,8 +860,8 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|||||||
if (browserSettings()->supportKphFields()) {
|
if (browserSettings()->supportKphFields()) {
|
||||||
const EntryAttributes* attr = entry->attributes();
|
const EntryAttributes* attr = entry->attributes();
|
||||||
QJsonArray stringFields;
|
QJsonArray stringFields;
|
||||||
for (const QString& key : attr->keys()) {
|
for (const auto& key : attr->keys()) {
|
||||||
if (key.startsWith(QLatin1String("KPH: "))) {
|
if (key.startsWith("KPH: ")) {
|
||||||
QJsonObject sField;
|
QJsonObject sField;
|
||||||
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
||||||
stringFields.append(sField);
|
stringFields.append(sField);
|
||||||
@ -899,17 +906,15 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& sele
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
|
|
||||||
|
|
||||||
for (auto* g : rootGroup->groupsRecursive(true)) {
|
for (auto* g : rootGroup->groupsRecursive(true)) {
|
||||||
if (g->name() == groupName && !g->isRecycled()) {
|
if (g->name() == KEEPASSXCBROWSER_GROUP_NAME && !g->isRecycled()) {
|
||||||
return db->rootGroup()->findGroupByUuid(g->uuid());
|
return db->rootGroup()->findGroupByUuid(g->uuid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* group = new Group();
|
auto* group = new Group();
|
||||||
group->setUuid(QUuid::createUuid());
|
group->setUuid(QUuid::createUuid());
|
||||||
group->setName(groupName);
|
group->setName(KEEPASSXCBROWSER_GROUP_NAME);
|
||||||
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||||
group->setParent(rootGroup);
|
group->setParent(rootGroup);
|
||||||
return group;
|
return group;
|
||||||
@ -987,6 +992,26 @@ bool BrowserService::removeFirstDomain(QString& hostname)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BrowserService::handleURL(const QString& entryUrl, const QString& hostname, const QString& url)
|
||||||
|
{
|
||||||
|
QUrl entryQUrl(entryUrl);
|
||||||
|
QString entryScheme = entryQUrl.scheme();
|
||||||
|
QUrl qUrl(url);
|
||||||
|
|
||||||
|
// Ignore entry if port or scheme defined in the URL doesn't match
|
||||||
|
if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port())
|
||||||
|
|| (browserSettings()->matchUrlScheme() && !entryScheme.isEmpty() && entryScheme.compare(qUrl.scheme()) != 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to match hostname in URL field
|
||||||
|
if ((!entryUrl.isEmpty() && hostname.contains(entryUrl))
|
||||||
|
|| (matchUrlScheme(entryUrl) && hostname.endsWith(entryQUrl.host()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the base domain of URL.
|
* Gets the base domain of URL.
|
||||||
*
|
*
|
||||||
@ -1080,9 +1105,8 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Data
|
|||||||
publicKey.remove(LEGACY_ASSOCIATE_KEY_PREFIX);
|
publicKey.remove(LEGACY_ASSOCIATE_KEY_PREFIX);
|
||||||
|
|
||||||
// Add key to database custom data
|
// Add key to database custom data
|
||||||
if (db && !db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey)) {
|
if (db && !db->metadata()->customData()->contains(ASSOCIATE_KEY_PREFIX + publicKey)) {
|
||||||
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey,
|
db->metadata()->customData()->set(ASSOCIATE_KEY_PREFIX + publicKey, entry->attributes()->value(key));
|
||||||
entry->attributes()->value(key));
|
|
||||||
++keyCounter;
|
++keyCounter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,12 +68,13 @@ public:
|
|||||||
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
|
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const char KEEPASSXCBROWSER_NAME[];
|
static const QString KEEPASSXCBROWSER_NAME;
|
||||||
static const char KEEPASSXCBROWSER_OLD_NAME[];
|
static const QString KEEPASSXCBROWSER_OLD_NAME;
|
||||||
static const char ASSOCIATE_KEY_PREFIX[];
|
static const QString ASSOCIATE_KEY_PREFIX;
|
||||||
static const char LEGACY_ASSOCIATE_KEY_PREFIX[];
|
static const QString LEGACY_ASSOCIATE_KEY_PREFIX;
|
||||||
static const char OPTION_SKIP_AUTO_SUBMIT[];
|
static const QString OPTION_SKIP_AUTO_SUBMIT;
|
||||||
static const char OPTION_HIDE_ENTRY[];
|
static const QString OPTION_HIDE_ENTRY;
|
||||||
|
static const QString ADDITIONAL_URL;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QJsonArray findMatchingEntries(const QString& id,
|
QJsonArray findMatchingEntries(const QString& id,
|
||||||
@ -129,6 +130,7 @@ private:
|
|||||||
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
|
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
|
||||||
bool matchUrlScheme(const QString& url);
|
bool matchUrlScheme(const QString& url);
|
||||||
bool removeFirstDomain(QString& hostname);
|
bool removeFirstDomain(QString& hostname);
|
||||||
|
bool handleURL(const QString& entryUrl, const QString& hostname, const QString& url);
|
||||||
QString baseDomain(const QString& url) const;
|
QString baseDomain(const QString& url) const;
|
||||||
QSharedPointer<Database> getDatabase();
|
QSharedPointer<Database> getDatabase();
|
||||||
QSharedPointer<Database> selectedDatabase();
|
QSharedPointer<Database> selectedDatabase();
|
||||||
|
@ -257,7 +257,7 @@ namespace Bootstrap
|
|||||||
nullptr, // do not change owner or group
|
nullptr, // do not change owner or group
|
||||||
pACL, // DACL specified
|
pACL, // DACL specified
|
||||||
nullptr // do not change SACL
|
nullptr // do not change SACL
|
||||||
);
|
);
|
||||||
|
|
||||||
Cleanup:
|
Cleanup:
|
||||||
|
|
||||||
|
@ -762,7 +762,8 @@ Entry* Entry::clone(CloneFlags flags) const
|
|||||||
entry->m_autoTypeAssociations->copyDataFrom(m_autoTypeAssociations);
|
entry->m_autoTypeAssociations->copyDataFrom(m_autoTypeAssociations);
|
||||||
if (flags & CloneIncludeHistory) {
|
if (flags & CloneIncludeHistory) {
|
||||||
for (Entry* historyItem : m_history) {
|
for (Entry* historyItem : m_history) {
|
||||||
Entry* historyItemClone = historyItem->clone(flags & ~CloneIncludeHistory & ~CloneNewUuid & ~CloneResetTimeInfo);
|
Entry* historyItemClone =
|
||||||
|
historyItem->clone(flags & ~CloneIncludeHistory & ~CloneNewUuid & ~CloneResetTimeInfo);
|
||||||
historyItemClone->setUpdateTimeinfo(false);
|
historyItemClone->setUpdateTimeinfo(false);
|
||||||
historyItemClone->setUuid(entry->uuid());
|
historyItemClone->setUuid(entry->uuid());
|
||||||
historyItemClone->setUpdateTimeinfo(true);
|
historyItemClone->setUpdateTimeinfo(true);
|
||||||
|
@ -242,12 +242,12 @@ void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData()
|
|||||||
{
|
{
|
||||||
if (MessageBox::Yes
|
if (MessageBox::Yes
|
||||||
!= MessageBox::question(
|
!= MessageBox::question(
|
||||||
this,
|
this,
|
||||||
tr("Move KeePassHTTP attributes to custom data"),
|
tr("Move KeePassHTTP attributes to custom data"),
|
||||||
tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
|
tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
|
||||||
"This is necessary to maintain compatibility with the browser plugin."),
|
"This is necessary to maintain compatibility with the browser plugin."),
|
||||||
MessageBox::Yes | MessageBox::Cancel,
|
MessageBox::Yes | MessageBox::Cancel,
|
||||||
MessageBox::Cancel)) {
|
MessageBox::Cancel)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include "sshagent/SSHAgent.h"
|
#include "sshagent/SSHAgent.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
|
#include "EntryURLModel.h"
|
||||||
#include "browser/BrowserService.h"
|
#include "browser/BrowserService.h"
|
||||||
#endif
|
#endif
|
||||||
#include "gui/Clipboard.h"
|
#include "gui/Clipboard.h"
|
||||||
@ -82,7 +83,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
|
|||||||
, m_sshAgentWidget(new QWidget())
|
, m_sshAgentWidget(new QWidget())
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
|
, m_browserSettingsChanged(false)
|
||||||
, m_browserWidget(new QWidget())
|
, m_browserWidget(new QWidget())
|
||||||
|
, m_additionalURLsDataModel(new EntryURLModel(this))
|
||||||
#endif
|
#endif
|
||||||
, m_editWidgetProperties(new EditWidgetProperties())
|
, m_editWidgetProperties(new EditWidgetProperties())
|
||||||
, m_historyWidget(new QWidget())
|
, m_historyWidget(new QWidget())
|
||||||
@ -265,18 +268,112 @@ void EditEntryWidget::setupBrowser()
|
|||||||
|
|
||||||
if (config()->get("Browser/Enabled", false).toBool()) {
|
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||||
addPage(tr("Browser Integration"), FilePath::instance()->icon("apps", "internet-web-browser"), m_browserWidget);
|
addPage(tr("Browser Integration"), FilePath::instance()->icon("apps", "internet-web-browser"), m_browserWidget);
|
||||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowser()));
|
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
|
||||||
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowser()));
|
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
||||||
|
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
|
||||||
|
connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
|
||||||
|
connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
|
||||||
|
connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
|
||||||
|
connect(m_browserUi->additionalURLsView->selectionModel(),
|
||||||
|
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
|
||||||
|
SLOT(updateCurrentURL()));
|
||||||
|
connect(m_additionalURLsDataModel,
|
||||||
|
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
|
||||||
|
SLOT(updateCurrentAttribute()));
|
||||||
|
// clang-format on
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditEntryWidget::updateBrowserModified()
|
||||||
|
{
|
||||||
|
m_browserSettingsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
void EditEntryWidget::updateBrowser()
|
void EditEntryWidget::updateBrowser()
|
||||||
{
|
{
|
||||||
|
if (!m_browserSettingsChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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")));
|
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? QString("true") : QString("false")));
|
||||||
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? QString("true") : QString("false")));
|
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? QString("true") : QString("false")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditEntryWidget::insertURL()
|
||||||
|
{
|
||||||
|
Q_ASSERT(!m_history);
|
||||||
|
|
||||||
|
QString name("KP2A_URL");
|
||||||
|
int i = 1;
|
||||||
|
|
||||||
|
while (m_entryAttributes->keys().contains(name)) {
|
||||||
|
name = QString("KP2A_URL_%1").arg(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entryAttributes->set(name, tr("<empty URL>"));
|
||||||
|
QModelIndex index = m_additionalURLsDataModel->indexByKey(name);
|
||||||
|
|
||||||
|
m_browserUi->additionalURLsView->setCurrentIndex(index);
|
||||||
|
m_browserUi->additionalURLsView->edit(index);
|
||||||
|
|
||||||
|
setModified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditEntryWidget::removeCurrentURL()
|
||||||
|
{
|
||||||
|
Q_ASSERT(!m_history);
|
||||||
|
|
||||||
|
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
|
||||||
|
|
||||||
|
if (index.isValid()) {
|
||||||
|
auto result = MessageBox::question(this,
|
||||||
|
tr("Confirm Removal"),
|
||||||
|
tr("Are you sure you want to remove this URL?"),
|
||||||
|
MessageBox::Remove | MessageBox::Cancel,
|
||||||
|
MessageBox::Cancel);
|
||||||
|
|
||||||
|
if (result == MessageBox::Remove) {
|
||||||
|
m_entryAttributes->remove(m_additionalURLsDataModel->keyByIndex(index));
|
||||||
|
if (m_additionalURLsDataModel->rowCount() == 0) {
|
||||||
|
m_browserUi->editURLButton->setEnabled(false);
|
||||||
|
m_browserUi->removeURLButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
setModified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditEntryWidget::editCurrentURL()
|
||||||
|
{
|
||||||
|
Q_ASSERT(!m_history);
|
||||||
|
|
||||||
|
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
|
||||||
|
|
||||||
|
if (index.isValid()) {
|
||||||
|
m_browserUi->additionalURLsView->edit(index);
|
||||||
|
setModified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditEntryWidget::updateCurrentURL()
|
||||||
|
{
|
||||||
|
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
|
||||||
|
|
||||||
|
if (index.isValid()) {
|
||||||
|
// Don't allow editing in history view
|
||||||
|
m_browserUi->editURLButton->setEnabled(!m_history);
|
||||||
|
m_browserUi->removeURLButton->setEnabled(!m_history);
|
||||||
|
} else {
|
||||||
|
m_browserUi->editURLButton->setEnabled(false);
|
||||||
|
m_browserUi->removeURLButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void EditEntryWidget::setupProperties()
|
void EditEntryWidget::setupProperties()
|
||||||
@ -366,8 +463,11 @@ void EditEntryWidget::setupEntryUpdate()
|
|||||||
|
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
if (config()->get("Browser/Enabled", false).toBool()) {
|
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
connect(m_browserUi->hideEntryCheckbox, 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->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -862,7 +962,8 @@ 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)) {
|
||||||
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");
|
||||||
} else {
|
} else {
|
||||||
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
|
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
|
||||||
}
|
}
|
||||||
@ -872,6 +973,15 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
|
|||||||
} else {
|
} else {
|
||||||
m_browserUi->hideEntryCheckbox->setChecked(false);
|
m_browserUi->hideEntryCheckbox->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_browserUi->addURLButton->setEnabled(!m_history);
|
||||||
|
m_browserUi->removeURLButton->setEnabled(false);
|
||||||
|
m_browserUi->editURLButton->setEnabled(false);
|
||||||
|
m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
|
||||||
|
|
||||||
|
if (m_additionalURLsDataModel->rowCount() != 0) {
|
||||||
|
m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
|
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
|
||||||
@ -946,6 +1056,12 @@ bool EditEntryWidget::commitEntry()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_XC_BROWSER
|
||||||
|
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||||
|
updateBrowser();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!m_create) {
|
if (!m_create) {
|
||||||
m_entry->beginUpdate();
|
m_entry->beginUpdate();
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,9 @@ class QStringListModel;
|
|||||||
#include "sshagent/KeeAgentSettings.h"
|
#include "sshagent/KeeAgentSettings.h"
|
||||||
class OpenSSHKey;
|
class OpenSSHKey;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_XC_BROWSER
|
||||||
|
class EntryURLModel;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
@ -120,7 +123,12 @@ private slots:
|
|||||||
void copyPublicKey();
|
void copyPublicKey();
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
|
void updateBrowserModified();
|
||||||
void updateBrowser();
|
void updateBrowser();
|
||||||
|
void insertURL();
|
||||||
|
void removeCurrentURL();
|
||||||
|
void editCurrentURL();
|
||||||
|
void updateCurrentURL();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -175,7 +183,9 @@ private:
|
|||||||
QWidget* const m_sshAgentWidget;
|
QWidget* const m_sshAgentWidget;
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
|
bool m_browserSettingsChanged;
|
||||||
QWidget* const m_browserWidget;
|
QWidget* const m_browserWidget;
|
||||||
|
EntryURLModel* const m_additionalURLsDataModel;
|
||||||
#endif
|
#endif
|
||||||
EditWidgetProperties* const m_editWidgetProperties;
|
EditWidgetProperties* const m_editWidgetProperties;
|
||||||
QWidget* const m_historyWidget;
|
QWidget* const m_historyWidget;
|
||||||
|
@ -54,26 +54,86 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_3">
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
<property name="orientation">
|
<property name="title">
|
||||||
<enum>Qt::Vertical</enum>
|
<string>Additional URL's</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||||
<enum>QSizePolicy::Expanding</enum>
|
<item>
|
||||||
</property>
|
<widget class="QListView" name="additionalURLsView">
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>0</width>
|
||||||
<height>40</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<property name="resizeMode">
|
||||||
|
<enum>QListView::Adjust</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="additionalURLsButtonLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="addURLButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="removeURLButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="editURLButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Edit</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Expanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>skipAutoSubmitCheckbox</tabstop>
|
<tabstop>skipAutoSubmitCheckbox</tabstop>
|
||||||
<tabstop>hideEntryCheckbox</tabstop>
|
<tabstop>hideEntryCheckbox</tabstop>
|
||||||
|
<tabstop>additionalURLsView</tabstop>
|
||||||
|
<tabstop>addURLButton</tabstop>
|
||||||
|
<tabstop>removeURLButton</tabstop>
|
||||||
|
<tabstop>editURLButton</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
120
src/gui/entry/EntryURLModel.cpp
Normal file
120
src/gui/entry/EntryURLModel.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 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 "EntryURLModel.h"
|
||||||
|
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
EntryURLModel::EntryURLModel(QObject* parent)
|
||||||
|
: QStandardItemModel(parent)
|
||||||
|
, m_entryAttributes(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntryURLModel::setEntryAttributes(EntryAttributes* entryAttributes)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
if (m_entryAttributes) {
|
||||||
|
m_entryAttributes->disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entryAttributes = entryAttributes;
|
||||||
|
|
||||||
|
if (m_entryAttributes) {
|
||||||
|
updateAttributes();
|
||||||
|
// clang-format off
|
||||||
|
connect(m_entryAttributes, SIGNAL(added(QString)), SLOT(updateAttributes()));
|
||||||
|
connect(m_entryAttributes, SIGNAL(customKeyModified(QString)), SLOT(updateAttributes()));
|
||||||
|
connect(m_entryAttributes, SIGNAL(removed(QString)), SLOT(updateAttributes()));
|
||||||
|
connect(m_entryAttributes, SIGNAL(renamed(QString,QString)), SLOT(updateAttributes()));
|
||||||
|
connect(m_entryAttributes, SIGNAL(reset()), SLOT(updateAttributes()));
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntryURLModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
|
{
|
||||||
|
if (!index.isValid() || role != Qt::EditRole || value.type() != QVariant::String || value.toString().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int row = index.row();
|
||||||
|
const QString key = m_urls.at(row).first;
|
||||||
|
const QString oldValue = m_urls.at(row).second;
|
||||||
|
|
||||||
|
if (EntryAttributes::isDefaultAttribute(key) || m_entryAttributes->containsValue(value.toString())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entryAttributes->set(key, value.toString());
|
||||||
|
|
||||||
|
emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex EntryURLModel::indexByKey(const QString& key) const
|
||||||
|
{
|
||||||
|
int row = -1;
|
||||||
|
for (int i = 0; i < m_urls.size(); ++i) {
|
||||||
|
if (m_urls.at(i).first == key) {
|
||||||
|
row = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row == -1) {
|
||||||
|
return QModelIndex();
|
||||||
|
} else {
|
||||||
|
return index(row, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EntryURLModel::keyByIndex(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return QString();
|
||||||
|
} else {
|
||||||
|
return m_urls.at(index.row()).first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntryURLModel::updateAttributes()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
m_urls.clear();
|
||||||
|
|
||||||
|
const QList<QString> attributesKeyList = m_entryAttributes->keys();
|
||||||
|
for (const QString& key : attributesKeyList) {
|
||||||
|
if (!EntryAttributes::isDefaultAttribute(key) && key.contains("KP2A_URL")) {
|
||||||
|
const auto value = m_entryAttributes->value(key);
|
||||||
|
m_urls.append(qMakePair(key, value));
|
||||||
|
|
||||||
|
auto* item = new QStandardItem(value);
|
||||||
|
if (m_entryAttributes->isProtected(key)) {
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
|
||||||
|
}
|
||||||
|
appendRow(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/gui/entry/EntryURLModel.h
Normal file
46
src/gui/entry/EntryURLModel.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 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 KEEPASSXC_ENTRYURLMODEL_H
|
||||||
|
#define KEEPASSXC_ENTRYURLMODEL_H
|
||||||
|
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
class EntryAttributes;
|
||||||
|
|
||||||
|
class EntryURLModel : public QStandardItemModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit EntryURLModel(QObject* parent = nullptr);
|
||||||
|
void setEntryAttributes(EntryAttributes* entryAttributes);
|
||||||
|
void insertRow(const QString& key, const QString& value);
|
||||||
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||||
|
QModelIndex indexByKey(const QString& key) const;
|
||||||
|
QString keyByIndex(const QModelIndex& index) const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateAttributes();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QPair<QString, QString>> m_urls;
|
||||||
|
EntryAttributes* m_entryAttributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_ENTRYURLMODEL_H
|
@ -89,7 +89,7 @@ endmacro(add_unit_test)
|
|||||||
|
|
||||||
set(TEST_LIBRARIES
|
set(TEST_LIBRARIES
|
||||||
keepassx_core
|
keepassx_core
|
||||||
${keepasshttp_LIB}
|
${keepassxcbrowser_LIB}
|
||||||
${autotype_LIB}
|
${autotype_LIB}
|
||||||
Qt5::Core
|
Qt5::Core
|
||||||
Qt5::Concurrent
|
Qt5::Concurrent
|
||||||
|
@ -245,6 +245,40 @@ void TestBrowser::testSearchEntriesWithPort()
|
|||||||
QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
|
QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestBrowser::testSearchEntriesWithAdditionalURLs()
|
||||||
|
{
|
||||||
|
auto db = QSharedPointer<Database>::create();
|
||||||
|
auto* root = db->rootGroup();
|
||||||
|
|
||||||
|
QList<Entry*> entries;
|
||||||
|
QList<QString> urls;
|
||||||
|
urls.push_back("https://github.com/");
|
||||||
|
urls.push_back("https://www.example.com");
|
||||||
|
urls.push_back("http://domain.com");
|
||||||
|
|
||||||
|
for (int i = 0; i < urls.length(); ++i) {
|
||||||
|
auto entry = new Entry();
|
||||||
|
entry->setGroup(root);
|
||||||
|
entry->beginUpdate();
|
||||||
|
entry->setUrl(urls[i]);
|
||||||
|
entry->setUsername(QString("User %1").arg(i));
|
||||||
|
entry->endUpdate();
|
||||||
|
entries.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an additional URL to the first entry
|
||||||
|
entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://keepassxc.org");
|
||||||
|
|
||||||
|
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
|
||||||
|
QCOMPARE(result.length(), 1);
|
||||||
|
QCOMPARE(result[0]->url(), QString("https://github.com/"));
|
||||||
|
|
||||||
|
// Search the additional URL. It should return the same entry
|
||||||
|
auto additionalResult = m_browserService->searchEntries(db, "keepassxc.org", "https://keepassxc.org");
|
||||||
|
QCOMPARE(additionalResult.length(), 1);
|
||||||
|
QCOMPARE(additionalResult[0]->url(), QString("https://github.com/"));
|
||||||
|
}
|
||||||
|
|
||||||
void TestBrowser::testSortEntries()
|
void TestBrowser::testSortEntries()
|
||||||
{
|
{
|
||||||
auto db = QSharedPointer<Database>::create();
|
auto db = QSharedPointer<Database>::create();
|
||||||
|
@ -42,6 +42,7 @@ private slots:
|
|||||||
void testSortPriority();
|
void testSortPriority();
|
||||||
void testSearchEntries();
|
void testSearchEntries();
|
||||||
void testSearchEntriesWithPort();
|
void testSearchEntriesWithPort();
|
||||||
|
void testSearchEntriesWithAdditionalURLs();
|
||||||
void testSortEntries();
|
void testSortEntries();
|
||||||
void testGetDatabaseGroups();
|
void testGetDatabaseGroups();
|
||||||
|
|
||||||
|
@ -549,8 +549,7 @@ void TestCli::testCreate()
|
|||||||
m_stderrFile->reset();
|
m_stderrFile->reset();
|
||||||
m_stdoutFile->reset();
|
m_stdoutFile->reset();
|
||||||
|
|
||||||
QCOMPARE(m_stdoutFile->readLine(),
|
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
|
||||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||||
|
|
||||||
Utils::Test::setNextPassword("a");
|
Utils::Test::setNextPassword("a");
|
||||||
@ -578,8 +577,7 @@ void TestCli::testCreate()
|
|||||||
m_stdoutFile->seek(pos);
|
m_stdoutFile->seek(pos);
|
||||||
m_stderrFile->seek(errPos);
|
m_stderrFile->seek(errPos);
|
||||||
|
|
||||||
QCOMPARE(m_stdoutFile->readLine(),
|
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
|
||||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||||
|
|
||||||
Utils::Test::setNextPassword("a");
|
Utils::Test::setNextPassword("a");
|
||||||
@ -596,8 +594,7 @@ void TestCli::testCreate()
|
|||||||
m_stdoutFile->seek(pos);
|
m_stdoutFile->seek(pos);
|
||||||
m_stderrFile->seek(errPos);
|
m_stderrFile->seek(errPos);
|
||||||
|
|
||||||
QCOMPARE(m_stdoutFile->readLine(),
|
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
|
||||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||||
|
|
||||||
Utils::Test::setNextPassword("a");
|
Utils::Test::setNextPassword("a");
|
||||||
@ -1041,8 +1038,7 @@ void TestCli::testImport()
|
|||||||
importCmd.execute({"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet});
|
importCmd.execute({"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet});
|
||||||
m_stdoutFile->seek(pos);
|
m_stdoutFile->seek(pos);
|
||||||
|
|
||||||
QCOMPARE(m_stdoutFile->readAll(),
|
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
|
||||||
|
|
||||||
Utils::Test::setNextPassword("a");
|
Utils::Test::setNextPassword("a");
|
||||||
auto dbQuiet = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilenameQuiet, true, "", "", Utils::DEVNULL));
|
auto dbQuiet = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilenameQuiet, true, "", "", Utils::DEVNULL));
|
||||||
|
@ -124,7 +124,7 @@ void TestEntry::testClone()
|
|||||||
QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
|
QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
|
||||||
|
|
||||||
// Date back history of original entry
|
// Date back history of original entry
|
||||||
Entry * firstHistoryItem = entryOrg->historyItems()[0];
|
Entry* firstHistoryItem = entryOrg->historyItems()[0];
|
||||||
TimeInfo entryOrgHistoryTimeInfo = firstHistoryItem->timeInfo();
|
TimeInfo entryOrgHistoryTimeInfo = firstHistoryItem->timeInfo();
|
||||||
QDateTime datedBackEntryOrgModificationTime = entryOrgHistoryTimeInfo.lastModificationTime().addMSecs(-10);
|
QDateTime datedBackEntryOrgModificationTime = entryOrgHistoryTimeInfo.lastModificationTime().addMSecs(-10);
|
||||||
entryOrgHistoryTimeInfo.setLastModificationTime(datedBackEntryOrgModificationTime);
|
entryOrgHistoryTimeInfo.setLastModificationTime(datedBackEntryOrgModificationTime);
|
||||||
@ -140,9 +140,8 @@ void TestEntry::testClone()
|
|||||||
// Timeinfo of history items should not be modified
|
// Timeinfo of history items should not be modified
|
||||||
QList<Entry*> entryOrgHistory = entryOrg->historyItems(), clonedHistory = entryCloneHistory->historyItems();
|
QList<Entry*> entryOrgHistory = entryOrg->historyItems(), clonedHistory = entryCloneHistory->historyItems();
|
||||||
auto entryOrgHistoryItem = entryOrgHistory.constBegin();
|
auto entryOrgHistoryItem = entryOrgHistory.constBegin();
|
||||||
for(auto entryCloneHistoryItem = clonedHistory.constBegin()
|
for (auto entryCloneHistoryItem = clonedHistory.constBegin(); entryCloneHistoryItem != clonedHistory.constEnd();
|
||||||
;entryCloneHistoryItem != clonedHistory.constEnd()
|
++entryCloneHistoryItem, ++entryOrgHistoryItem) {
|
||||||
;++entryCloneHistoryItem, ++entryOrgHistoryItem) {
|
|
||||||
QCOMPARE((*entryOrgHistoryItem)->timeInfo(), (*entryCloneHistoryItem)->timeInfo());
|
QCOMPARE((*entryOrgHistoryItem)->timeInfo(), (*entryCloneHistoryItem)->timeInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QListView>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTableView>
|
#include <QTableView>
|
||||||
#include <QToolBar>
|
#include <QToolBar>
|
||||||
@ -128,6 +130,9 @@ void TestGuiBrowser::cleanupTestCase()
|
|||||||
|
|
||||||
void TestGuiBrowser::testEntrySettings()
|
void TestGuiBrowser::testEntrySettings()
|
||||||
{
|
{
|
||||||
|
// Enable the Browser Integration
|
||||||
|
config()->set("Browser/Enabled", true);
|
||||||
|
|
||||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||||
|
|
||||||
@ -146,7 +151,7 @@ void TestGuiBrowser::testEntrySettings()
|
|||||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||||
|
|
||||||
// Switch to Properties page and select all rows from the custom data table
|
// Switch to Properties page and select all rows from the custom data table
|
||||||
editEntryWidget->setCurrentPage(4);
|
editEntryWidget->setCurrentPage(5);
|
||||||
auto customDataTableView = editEntryWidget->findChild<QTableView*>("customDataTable");
|
auto customDataTableView = editEntryWidget->findChild<QTableView*>("customDataTable");
|
||||||
QVERIFY(customDataTableView);
|
QVERIFY(customDataTableView);
|
||||||
QTest::mouseClick(customDataTableView, Qt::LeftButton);
|
QTest::mouseClick(customDataTableView, Qt::LeftButton);
|
||||||
@ -171,6 +176,56 @@ void TestGuiBrowser::testEntrySettings()
|
|||||||
QCOMPARE(entry->customData()->size(), 0);
|
QCOMPARE(entry->customData()->size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestGuiBrowser::testAdditionalURLs()
|
||||||
|
{
|
||||||
|
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||||
|
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||||
|
|
||||||
|
entryView->setFocus();
|
||||||
|
QVERIFY(entryView->hasFocus());
|
||||||
|
|
||||||
|
// Select the first entry in the database
|
||||||
|
QModelIndex entryItem = entryView->model()->index(0, 1);
|
||||||
|
clickIndex(entryItem, entryView, Qt::LeftButton);
|
||||||
|
|
||||||
|
auto* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||||
|
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||||
|
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||||
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
||||||
|
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||||
|
|
||||||
|
// Switch to Browser Integration page and add three URL's
|
||||||
|
editEntryWidget->setCurrentPage(4);
|
||||||
|
auto* addURLButton = editEntryWidget->findChild<QPushButton*>("addURLButton");
|
||||||
|
QVERIFY(addURLButton);
|
||||||
|
|
||||||
|
auto* urlList = editEntryWidget->findChild<QListView*>("additionalURLsView");
|
||||||
|
QVERIFY(urlList);
|
||||||
|
|
||||||
|
QStringList testURLs = {"https://example1.com", "https://example2.com", "https://example3.com"};
|
||||||
|
|
||||||
|
for (const auto& url : testURLs) {
|
||||||
|
QTest::mouseClick(addURLButton, Qt::LeftButton);
|
||||||
|
QApplication::processEvents();
|
||||||
|
QTest::keyClicks(urlList->focusWidget(), url);
|
||||||
|
QTest::keyClick(urlList->focusWidget(), Qt::Key_Enter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the values from attributesEdit
|
||||||
|
editEntryWidget->setCurrentPage(1);
|
||||||
|
auto* attributesView = editEntryWidget->findChild<QListView*>("attributesView");
|
||||||
|
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||||
|
|
||||||
|
// Go top of the list
|
||||||
|
attributesView->setFocus();
|
||||||
|
QTest::keyClick(attributesView->focusWidget(), Qt::Key_PageUp);
|
||||||
|
|
||||||
|
for (const auto& url : testURLs) {
|
||||||
|
QCOMPARE(attrTextEdit->toPlainText(), url);
|
||||||
|
QTest::keyClick(attributesView->focusWidget(), Qt::Key_Down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TestGuiBrowser::triggerAction(const QString& name)
|
void TestGuiBrowser::triggerAction(const QString& name)
|
||||||
{
|
{
|
||||||
auto* action = m_mainWindow->findChild<QAction*>(name);
|
auto* action = m_mainWindow->findChild<QAction*>(name);
|
||||||
|
@ -44,6 +44,7 @@ private slots:
|
|||||||
void cleanupTestCase();
|
void cleanupTestCase();
|
||||||
|
|
||||||
void testEntrySettings();
|
void testEntrySettings();
|
||||||
|
void testAdditionalURLs();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void triggerAction(const QString& name);
|
void triggerAction(const QString& name);
|
||||||
|
Loading…
Reference in New Issue
Block a user