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)
|
||||
set(keepassxcbrowser_LIB keepassxcbrowser)
|
||||
set(keepassx_SOURCES ${keepassx_SOURCES} gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp)
|
||||
set(keepassx_SOURCES ${keepassx_SOURCES} gui/entry/EntryURLModel.cpp)
|
||||
endif()
|
||||
|
||||
add_subdirectory(autotype)
|
||||
|
@ -53,12 +53,6 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
||||
|
||||
m_ui->scriptWarningWidget->setVisible(false);
|
||||
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!"),
|
||||
MessageWidget::Warning);
|
||||
@ -154,9 +148,13 @@ void BrowserOptionDialog::loadSettings()
|
||||
// Check for native messaging host location errors
|
||||
QString path;
|
||||
if (!settings->checkIfProxyExists(path)) {
|
||||
QString text = m_ui->scriptWarningWidget->text();
|
||||
text.append(path);
|
||||
m_ui->scriptWarningWidget->setText(text);
|
||||
auto text =
|
||||
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: %1")
|
||||
.arg(path);
|
||||
m_ui->scriptWarningWidget->showMessage(text, MessageWidget::Warning);
|
||||
m_ui->scriptWarningWidget->setVisible(true);
|
||||
} else {
|
||||
m_ui->scriptWarningWidget->setVisible(false);
|
||||
|
@ -41,18 +41,20 @@
|
||||
#include "gui/macutils/MacUtils.h"
|
||||
#endif
|
||||
|
||||
const char BrowserService::KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
|
||||
const char BrowserService::KEEPASSXCBROWSER_OLD_NAME[] = "keepassxc-browser Settings";
|
||||
const char BrowserService::ASSOCIATE_KEY_PREFIX[] = "KPXC_BROWSER_";
|
||||
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
|
||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
||||
const QString BrowserService::ASSOCIATE_KEY_PREFIX = QStringLiteral("KPXC_BROWSER_");
|
||||
static const QString KEEPASSXCBROWSER_GROUP_NAME = QStringLiteral("KeePassXC-Browser Passwords");
|
||||
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
||||
// These are for the settings and password conversion
|
||||
const char BrowserService::LEGACY_ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
|
||||
static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings";
|
||||
static const char KEEPASSHTTP_GROUP_NAME[] = "KeePassHttp Passwords";
|
||||
const QString BrowserService::LEGACY_ASSOCIATE_KEY_PREFIX = QStringLiteral("Public Key: ");
|
||||
static const QString KEEPASSHTTP_NAME = QStringLiteral("KeePassHttp Settings");
|
||||
static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwords");
|
||||
// Extra entry related options saved in custom data
|
||||
const char BrowserService::OPTION_SKIP_AUTO_SUBMIT[] = "BrowserSkipAutoSubmit";
|
||||
const char BrowserService::OPTION_HIDE_ENTRY[] = "BrowserHideEntry";
|
||||
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
|
||||
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
|
||||
// Multiple URL's
|
||||
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
|
||||
|
||||
BrowserService::BrowserService(DatabaseTabWidget* parent)
|
||||
: m_dbTabWidget(parent)
|
||||
@ -320,7 +322,7 @@ QString BrowserService::storeKey(const QString& key)
|
||||
return {};
|
||||
}
|
||||
|
||||
contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
||||
contains = db->metadata()->customData()->contains(ASSOCIATE_KEY_PREFIX + id);
|
||||
if (contains) {
|
||||
dialogResult = MessageBox::warning(nullptr,
|
||||
tr("KeePassXC: Overwrite existing key?"),
|
||||
@ -333,7 +335,7 @@ QString BrowserService::storeKey(const QString& key)
|
||||
} while (contains && dialogResult == MessageBox::Cancel);
|
||||
|
||||
hideWindow();
|
||||
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key);
|
||||
db->metadata()->customData()->set(ASSOCIATE_KEY_PREFIX + id, key);
|
||||
return id;
|
||||
}
|
||||
|
||||
@ -344,7 +346,7 @@ QString BrowserService::getKey(const QString& id)
|
||||
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,
|
||||
@ -377,9 +379,9 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
// Check entries for authorization
|
||||
QList<Entry*> pwEntriesToConfirm;
|
||||
QList<Entry*> pwEntries;
|
||||
for (Entry* entry : searchEntries(url, keyList)) {
|
||||
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY) &&
|
||||
entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
|
||||
for (auto* entry : searchEntries(url, keyList)) {
|
||||
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
|
||||
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -425,7 +427,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||
|
||||
// Fill the list
|
||||
for (Entry* entry : pwEntries) {
|
||||
for (auto* entry : pwEntries) {
|
||||
result.append(prepareEntry(entry));
|
||||
}
|
||||
|
||||
@ -588,22 +590,30 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
|
||||
return entries;
|
||||
}
|
||||
|
||||
for (Entry* entry : EntrySearcher().search(baseDomain(hostname), rootGroup)) {
|
||||
QString entryUrl = entry->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)) {
|
||||
for (const auto& group : rootGroup->groupsRecursive(true)) {
|
||||
if (group->isRecycled() || !group->resolveSearchingEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter to match hostname in URL field
|
||||
if ((!entryUrl.isEmpty() && hostname.contains(entryUrl))
|
||||
|| (matchUrlScheme(entryUrl) && hostname.endsWith(entryQUrl.host()))) {
|
||||
for (auto* entry : group->entries()) {
|
||||
if (entry->isRecycled()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -616,7 +626,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
|
||||
// Check if database is connected with KeePassXC-Browser
|
||||
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@ -668,7 +678,7 @@ void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database
|
||||
|
||||
int counter = 0;
|
||||
int keyCounter = 0;
|
||||
for (Entry* entry : entries) {
|
||||
for (auto* entry : entries) {
|
||||
if (progress.wasCanceled()) {
|
||||
return;
|
||||
}
|
||||
@ -721,12 +731,9 @@ void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database
|
||||
return;
|
||||
}
|
||||
|
||||
const QString keePassBrowserGroupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
|
||||
const QString keePassHttpGroupName = QLatin1String(KEEPASSHTTP_GROUP_NAME);
|
||||
|
||||
for (Group* g : rootGroup->groupsRecursive(true)) {
|
||||
if (g->name() == keePassHttpGroupName) {
|
||||
g->setName(keePassBrowserGroupName);
|
||||
for (auto* g : rootGroup->groupsRecursive(true)) {
|
||||
if (g->name() == KEEPASSHTTP_GROUP_NAME) {
|
||||
g->setName(KEEPASSXCBROWSER_GROUP_NAME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -745,7 +752,7 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
|
||||
|
||||
// Build map of prioritized entries
|
||||
QMultiMap<int, Entry*> priorities;
|
||||
for (Entry* entry : pwEntries) {
|
||||
for (auto* entry : pwEntries) {
|
||||
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
|
||||
}
|
||||
|
||||
@ -801,7 +808,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
|
||||
int res = accessControlDialog.exec();
|
||||
if (accessControlDialog.remember()) {
|
||||
for (Entry* entry : pwEntriesToConfirm) {
|
||||
for (auto* entry : pwEntriesToConfirm) {
|
||||
BrowserEntryConfig config;
|
||||
config.load(entry);
|
||||
if (res == QDialog::Accepted) {
|
||||
@ -853,8 +860,8 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
||||
if (browserSettings()->supportKphFields()) {
|
||||
const EntryAttributes* attr = entry->attributes();
|
||||
QJsonArray stringFields;
|
||||
for (const QString& key : attr->keys()) {
|
||||
if (key.startsWith(QLatin1String("KPH: "))) {
|
||||
for (const auto& key : attr->keys()) {
|
||||
if (key.startsWith("KPH: ")) {
|
||||
QJsonObject sField;
|
||||
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
||||
stringFields.append(sField);
|
||||
@ -899,17 +906,15 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& sele
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
auto* group = new Group();
|
||||
group->setUuid(QUuid::createUuid());
|
||||
group->setName(groupName);
|
||||
group->setName(KEEPASSXCBROWSER_GROUP_NAME);
|
||||
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
group->setParent(rootGroup);
|
||||
return group;
|
||||
@ -987,6 +992,26 @@ bool BrowserService::removeFirstDomain(QString& hostname)
|
||||
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.
|
||||
*
|
||||
@ -1080,9 +1105,8 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Data
|
||||
publicKey.remove(LEGACY_ASSOCIATE_KEY_PREFIX);
|
||||
|
||||
// Add key to database custom data
|
||||
if (db && !db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey)) {
|
||||
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey,
|
||||
entry->attributes()->value(key));
|
||||
if (db && !db->metadata()->customData()->contains(ASSOCIATE_KEY_PREFIX + publicKey)) {
|
||||
db->metadata()->customData()->set(ASSOCIATE_KEY_PREFIX + publicKey, entry->attributes()->value(key));
|
||||
++keyCounter;
|
||||
}
|
||||
}
|
||||
|
@ -68,12 +68,13 @@ public:
|
||||
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
|
||||
|
||||
public:
|
||||
static const char KEEPASSXCBROWSER_NAME[];
|
||||
static const char KEEPASSXCBROWSER_OLD_NAME[];
|
||||
static const char ASSOCIATE_KEY_PREFIX[];
|
||||
static const char LEGACY_ASSOCIATE_KEY_PREFIX[];
|
||||
static const char OPTION_SKIP_AUTO_SUBMIT[];
|
||||
static const char OPTION_HIDE_ENTRY[];
|
||||
static const QString KEEPASSXCBROWSER_NAME;
|
||||
static const QString KEEPASSXCBROWSER_OLD_NAME;
|
||||
static const QString ASSOCIATE_KEY_PREFIX;
|
||||
static const QString LEGACY_ASSOCIATE_KEY_PREFIX;
|
||||
static const QString OPTION_SKIP_AUTO_SUBMIT;
|
||||
static const QString OPTION_HIDE_ENTRY;
|
||||
static const QString ADDITIONAL_URL;
|
||||
|
||||
public slots:
|
||||
QJsonArray findMatchingEntries(const QString& id,
|
||||
@ -129,6 +130,7 @@ private:
|
||||
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
|
||||
bool matchUrlScheme(const QString& url);
|
||||
bool removeFirstDomain(QString& hostname);
|
||||
bool handleURL(const QString& entryUrl, const QString& hostname, const QString& url);
|
||||
QString baseDomain(const QString& url) const;
|
||||
QSharedPointer<Database> getDatabase();
|
||||
QSharedPointer<Database> selectedDatabase();
|
||||
|
@ -257,7 +257,7 @@ namespace Bootstrap
|
||||
nullptr, // do not change owner or group
|
||||
pACL, // DACL specified
|
||||
nullptr // do not change SACL
|
||||
);
|
||||
);
|
||||
|
||||
Cleanup:
|
||||
|
||||
|
@ -762,7 +762,8 @@ Entry* Entry::clone(CloneFlags flags) const
|
||||
entry->m_autoTypeAssociations->copyDataFrom(m_autoTypeAssociations);
|
||||
if (flags & CloneIncludeHistory) {
|
||||
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->setUuid(entry->uuid());
|
||||
historyItemClone->setUpdateTimeinfo(true);
|
||||
|
@ -242,12 +242,12 @@ void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData()
|
||||
{
|
||||
if (MessageBox::Yes
|
||||
!= MessageBox::question(
|
||||
this,
|
||||
tr("Move KeePassHTTP attributes to custom data"),
|
||||
tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
|
||||
"This is necessary to maintain compatibility with the browser plugin."),
|
||||
MessageBox::Yes | MessageBox::Cancel,
|
||||
MessageBox::Cancel)) {
|
||||
this,
|
||||
tr("Move KeePassHTTP attributes to custom data"),
|
||||
tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
|
||||
"This is necessary to maintain compatibility with the browser plugin."),
|
||||
MessageBox::Yes | MessageBox::Cancel,
|
||||
MessageBox::Cancel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#include "EntryURLModel.h"
|
||||
#include "browser/BrowserService.h"
|
||||
#endif
|
||||
#include "gui/Clipboard.h"
|
||||
@ -82,7 +83,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
|
||||
, m_sshAgentWidget(new QWidget())
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
, m_browserSettingsChanged(false)
|
||||
, m_browserWidget(new QWidget())
|
||||
, m_additionalURLsDataModel(new EntryURLModel(this))
|
||||
#endif
|
||||
, m_editWidgetProperties(new EditWidgetProperties())
|
||||
, m_historyWidget(new QWidget())
|
||||
@ -265,18 +268,112 @@ void EditEntryWidget::setupBrowser()
|
||||
|
||||
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||
addPage(tr("Browser Integration"), FilePath::instance()->icon("apps", "internet-web-browser"), m_browserWidget);
|
||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowser()));
|
||||
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowser()));
|
||||
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
|
||||
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()
|
||||
{
|
||||
if (!m_browserSettingsChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto skip = m_browserUi->skipAutoSubmitCheckbox->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_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
|
||||
|
||||
void EditEntryWidget::setupProperties()
|
||||
@ -366,8 +463,11 @@ void EditEntryWidget::setupEntryUpdate()
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
||||
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
||||
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), 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
|
||||
}
|
||||
@ -862,7 +962,8 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
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 {
|
||||
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
|
||||
}
|
||||
@ -872,6 +973,15 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
|
||||
} else {
|
||||
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
|
||||
|
||||
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
|
||||
@ -946,6 +1056,12 @@ bool EditEntryWidget::commitEntry()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
if (config()->get("Browser/Enabled", false).toBool()) {
|
||||
updateBrowser();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m_create) {
|
||||
m_entry->beginUpdate();
|
||||
}
|
||||
|
@ -46,6 +46,9 @@ class QStringListModel;
|
||||
#include "sshagent/KeeAgentSettings.h"
|
||||
class OpenSSHKey;
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
class EntryURLModel;
|
||||
#endif
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@ -120,7 +123,12 @@ private slots:
|
||||
void copyPublicKey();
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
void updateBrowserModified();
|
||||
void updateBrowser();
|
||||
void insertURL();
|
||||
void removeCurrentURL();
|
||||
void editCurrentURL();
|
||||
void updateCurrentURL();
|
||||
#endif
|
||||
|
||||
private:
|
||||
@ -175,7 +183,9 @@ private:
|
||||
QWidget* const m_sshAgentWidget;
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
bool m_browserSettingsChanged;
|
||||
QWidget* const m_browserWidget;
|
||||
EntryURLModel* const m_additionalURLsDataModel;
|
||||
#endif
|
||||
EditWidgetProperties* const m_editWidgetProperties;
|
||||
QWidget* const m_historyWidget;
|
||||
|
@ -54,26 +54,86 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<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>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Additional URL's</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="QListView" name="additionalURLsView">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<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>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>skipAutoSubmitCheckbox</tabstop>
|
||||
<tabstop>hideEntryCheckbox</tabstop>
|
||||
<tabstop>additionalURLsView</tabstop>
|
||||
<tabstop>addURLButton</tabstop>
|
||||
<tabstop>removeURLButton</tabstop>
|
||||
<tabstop>editURLButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<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
|
||||
keepassx_core
|
||||
${keepasshttp_LIB}
|
||||
${keepassxcbrowser_LIB}
|
||||
${autotype_LIB}
|
||||
Qt5::Core
|
||||
Qt5::Concurrent
|
||||
|
@ -245,6 +245,40 @@ void TestBrowser::testSearchEntriesWithPort()
|
||||
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()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
|
@ -42,6 +42,7 @@ private slots:
|
||||
void testSortPriority();
|
||||
void testSearchEntries();
|
||||
void testSearchEntriesWithPort();
|
||||
void testSearchEntriesWithAdditionalURLs();
|
||||
void testSortEntries();
|
||||
void testGetDatabaseGroups();
|
||||
|
||||
|
@ -549,8 +549,7 @@ void TestCli::testCreate()
|
||||
m_stderrFile->reset();
|
||||
m_stdoutFile->reset();
|
||||
|
||||
QCOMPARE(m_stdoutFile->readLine(),
|
||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
@ -578,8 +577,7 @@ void TestCli::testCreate()
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stderrFile->seek(errPos);
|
||||
|
||||
QCOMPARE(m_stdoutFile->readLine(),
|
||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
@ -596,8 +594,7 @@ void TestCli::testCreate()
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stderrFile->seek(errPos);
|
||||
|
||||
QCOMPARE(m_stdoutFile->readLine(),
|
||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
@ -1041,8 +1038,7 @@ void TestCli::testImport()
|
||||
importCmd.execute({"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet});
|
||||
m_stdoutFile->seek(pos);
|
||||
|
||||
QCOMPARE(m_stdoutFile->readAll(),
|
||||
QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Enter password to encrypt database (optional): \n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
auto dbQuiet = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilenameQuiet, true, "", "", Utils::DEVNULL));
|
||||
|
@ -124,7 +124,7 @@ void TestEntry::testClone()
|
||||
QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
|
||||
|
||||
// Date back history of original entry
|
||||
Entry * firstHistoryItem = entryOrg->historyItems()[0];
|
||||
Entry* firstHistoryItem = entryOrg->historyItems()[0];
|
||||
TimeInfo entryOrgHistoryTimeInfo = firstHistoryItem->timeInfo();
|
||||
QDateTime datedBackEntryOrgModificationTime = entryOrgHistoryTimeInfo.lastModificationTime().addMSecs(-10);
|
||||
entryOrgHistoryTimeInfo.setLastModificationTime(datedBackEntryOrgModificationTime);
|
||||
@ -140,9 +140,8 @@ void TestEntry::testClone()
|
||||
// Timeinfo of history items should not be modified
|
||||
QList<Entry*> entryOrgHistory = entryOrg->historyItems(), clonedHistory = entryCloneHistory->historyItems();
|
||||
auto entryOrgHistoryItem = entryOrgHistory.constBegin();
|
||||
for(auto entryCloneHistoryItem = clonedHistory.constBegin()
|
||||
;entryCloneHistoryItem != clonedHistory.constEnd()
|
||||
;++entryCloneHistoryItem, ++entryOrgHistoryItem) {
|
||||
for (auto entryCloneHistoryItem = clonedHistory.constBegin(); entryCloneHistoryItem != clonedHistory.constEnd();
|
||||
++entryCloneHistoryItem, ++entryOrgHistoryItem) {
|
||||
QCOMPARE((*entryOrgHistoryItem)->timeInfo(), (*entryCloneHistoryItem)->timeInfo());
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include <QDebug>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListView>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTableView>
|
||||
#include <QToolBar>
|
||||
@ -128,6 +130,9 @@ void TestGuiBrowser::cleanupTestCase()
|
||||
|
||||
void TestGuiBrowser::testEntrySettings()
|
||||
{
|
||||
// Enable the Browser Integration
|
||||
config()->set("Browser/Enabled", true);
|
||||
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
@ -146,7 +151,7 @@ void TestGuiBrowser::testEntrySettings()
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
|
||||
// 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");
|
||||
QVERIFY(customDataTableView);
|
||||
QTest::mouseClick(customDataTableView, Qt::LeftButton);
|
||||
@ -171,6 +176,56 @@ void TestGuiBrowser::testEntrySettings()
|
||||
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)
|
||||
{
|
||||
auto* action = m_mainWindow->findChild<QAction*>(name);
|
||||
|
@ -44,6 +44,7 @@ private slots:
|
||||
void cleanupTestCase();
|
||||
|
||||
void testEntrySettings();
|
||||
void testAdditionalURLs();
|
||||
|
||||
private:
|
||||
void triggerAction(const QString& name);
|
||||
|
Loading…
Reference in New Issue
Block a user