Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/browser/BrowserService.cpp')
-rw-r--r--src/browser/BrowserService.cpp210
1 files changed, 138 insertions, 72 deletions
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
index 3916b99ad..2e5088c8a 100644
--- a/src/browser/BrowserService.cpp
+++ b/src/browser/BrowserService.cpp
@@ -17,6 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <QCheckBox>
#include <QInputDialog>
#include <QJsonArray>
#include <QMessageBox>
@@ -40,15 +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 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)
@@ -298,9 +304,9 @@ QString BrowserService::storeKey(const QString& key)
QInputDialog keyDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
- keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
- "If you would like to allow it access to your KeePassXC database,\n"
- "give it a unique name to identify and accept it."));
+ keyDialog.setLabelText(tr("You have received an association request for the following database:\n%1\n\n"
+ "Give the connection a unique name or ID, for example:\nchrome-laptop.")
+ .arg(db->metadata()->name().toHtmlEscaped()));
keyDialog.setOkButtonText(tr("Save and allow access"));
keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
raiseWindow();
@@ -316,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?"),
@@ -329,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;
}
@@ -340,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,
@@ -373,7 +379,12 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
// Check entries for authorization
QList<Entry*> pwEntriesToConfirm;
QList<Entry*> pwEntries;
- for (Entry* entry : searchEntries(url, keyList)) {
+ for (auto* entry : searchEntries(url, keyList)) {
+ if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
+ && entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
+ continue;
+ }
+
// HTTP Basic Auth always needs a confirmation
if (!ignoreHttpAuth && httpAuth) {
pwEntriesToConfirm.append(entry);
@@ -399,7 +410,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
}
// Confirm entries
- if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
+ if (confirmEntries(pwEntriesToConfirm, url, host, submitUrl, realm, httpAuth)) {
pwEntries.append(pwEntriesToConfirm);
}
@@ -416,8 +427,8 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
pwEntries = sortEntries(pwEntries, host, submitUrl);
// Fill the list
- for (Entry* entry : pwEntries) {
- result << prepareEntry(entry);
+ for (auto* entry : pwEntries) {
+ result.append(prepareEntry(entry));
}
return result;
@@ -489,17 +500,19 @@ void BrowserService::addEntry(const QString& id,
config.save(entry);
}
-void BrowserService::updateEntry(const QString& id,
- const QString& uuid,
- const QString& login,
- const QString& password,
- const QString& url,
- const QString& submitUrl)
+BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
+ const QString& uuid,
+ const QString& login,
+ const QString& password,
+ const QString& url,
+ const QString& submitUrl)
{
+ ReturnValue result = ReturnValue::Error;
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
"updateEntry",
Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(ReturnValue, result),
Q_ARG(QString, id),
Q_ARG(QString, uuid),
Q_ARG(QString, login),
@@ -510,14 +523,14 @@ void BrowserService::updateEntry(const QString& id,
auto db = selectedDatabase();
if (!db) {
- return;
+ return ReturnValue::Error;
}
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
if (!entry) {
// If entry is not found for update, add a new one to the selected database
addEntry(id, login, password, url, submitUrl, "", "", "", db);
- return;
+ return ReturnValue::Success;
}
// Check if the entry password is a reference. If so, update the original entry instead
@@ -526,14 +539,14 @@ void BrowserService::updateEntry(const QString& id,
if (!referenceUuid.isNull()) {
entry = db->rootGroup()->findEntryByUuid(referenceUuid);
if (!entry) {
- return;
+ return ReturnValue::Error;
}
}
}
QString username = entry->username();
if (username.isEmpty()) {
- return;
+ return ReturnValue::Error;
}
if (username.compare(login, Qt::CaseSensitive) != 0
@@ -557,10 +570,15 @@ void BrowserService::updateEntry(const QString& id,
}
entry->setPassword(password);
entry->endUpdate();
+ result = ReturnValue::Success;
+ } else {
+ result = ReturnValue::Canceled;
}
hideWindow();
}
+
+ return result;
}
QList<Entry*>
@@ -572,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);
}
}
@@ -597,6 +623,17 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPairList& keyList)
{
+ // 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(ASSOCIATE_KEY_PREFIX + keyPair.first);
+ if (!key.isEmpty() && keyPair.second == key) {
+ return true;
+ }
+ }
+ return false;
+ };
+
// Get the list of databases to search
QList<QSharedPointer<Database>> databases;
if (browserSettings()->searchInAllDatabases()) {
@@ -604,19 +641,16 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
for (int i = 0; i < count; ++i) {
if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
if (const auto& db = dbWidget->database()) {
- // Check if database is connected with KeePassXC-Browser
- for (const StringPair& keyPair : keyList) {
- QString key =
- db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
- if (!key.isEmpty() && keyPair.second == key) {
- databases << db;
- }
+ if (databaseConnected(db)) {
+ databases << db;
}
}
}
}
} else if (const auto& db = getDatabase()) {
- databases << db;
+ if (databaseConnected(db)) {
+ databases << db;
+ }
}
// Search entries matching the hostname
@@ -644,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;
}
@@ -697,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;
}
}
@@ -721,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);
}
@@ -755,8 +786,9 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& url,
const QString& host,
- const QString& submitHost,
- const QString& realm)
+ const QString& submitUrl,
+ const QString& realm,
+ const bool httpAuth)
{
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
return false;
@@ -765,17 +797,19 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
m_dialogActive = true;
BrowserAccessControlDialog accessControlDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
- accessControlDialog.setUrl(url);
+ accessControlDialog.setUrl(!submitUrl.isEmpty() ? submitUrl : url);
accessControlDialog.setItems(pwEntriesToConfirm);
+ accessControlDialog.setHTTPAuth(httpAuth);
raiseWindow();
accessControlDialog.show();
accessControlDialog.activateWindow();
accessControlDialog.raise();
+ const QString submitHost = QUrl(submitUrl).host();
int res = accessControlDialog.exec();
if (accessControlDialog.remember()) {
- for (Entry* entry : pwEntriesToConfirm) {
+ for (auto* entry : pwEntriesToConfirm) {
BrowserEntryConfig config;
config.load(entry);
if (res == QDialog::Accepted) {
@@ -820,14 +854,18 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
res["expired"] = "true";
}
+ if (entry->customData()->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
+ res["skipAutoSubmit"] = entry->customData()->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT);
+ }
+
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 << sField;
+ stringFields.append(sField);
}
}
res["stringFields"] = stringFields;
@@ -869,18 +907,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;
@@ -958,6 +993,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.
*
@@ -1051,9 +1106,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;
}
}
@@ -1064,6 +1118,10 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Data
bool BrowserService::checkLegacySettings()
{
+ if (!browserSettings()->isEnabled() || browserSettings()->noMigrationPrompt()) {
+ return false;
+ }
+
auto db = getDatabase();
if (!db) {
return false;
@@ -1083,13 +1141,21 @@ bool BrowserService::checkLegacySettings()
return false;
}
+ auto* checkbox = new QCheckBox(tr("Don't show this warning again"));
+ QObject::connect(checkbox, &QCheckBox::stateChanged, [&](int state) {
+ browserSettings()->setNoMigrationPrompt(static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked);
+ });
+
auto dialogResult =
MessageBox::warning(nullptr,
tr("KeePassXC: Legacy browser integration settings detected"),
tr("Your KeePassXC-Browser settings need to be moved into the database settings.\n"
"This is necessary to maintain your current browser connections.\n"
"Would you like to migrate your existing settings now?"),
- MessageBox::Yes | MessageBox::No);
+ MessageBox::Yes | MessageBox::No,
+ MessageBox::NoButton,
+ MessageBox::Raise,
+ checkbox);
return dialogResult == MessageBox::Yes;
}