2017-12-12 10:15:23 +02:00
|
|
|
/*
|
2018-11-01 04:27:38 +01:00
|
|
|
* Copyright (C) 2013 Francois Ferrand
|
|
|
|
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
|
|
|
* Copyright (C) 2017 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 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-03-23 11:18:06 +01:00
|
|
|
#include <QInputDialog>
|
2018-11-01 04:27:38 +01:00
|
|
|
#include <QJsonArray>
|
2018-03-23 11:18:06 +01:00
|
|
|
#include <QMessageBox>
|
2018-11-01 04:27:38 +01:00
|
|
|
#include <QProgressDialog>
|
2018-03-23 11:18:06 +01:00
|
|
|
#include <QUuid>
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
#include "BrowserAccessControlDialog.h"
|
2018-03-31 16:01:30 -04:00
|
|
|
#include "BrowserEntryConfig.h"
|
2018-10-19 21:44:08 +03:00
|
|
|
#include "BrowserEntrySaveDialog.h"
|
2018-11-01 04:27:38 +01:00
|
|
|
#include "BrowserService.h"
|
2018-03-31 16:01:30 -04:00
|
|
|
#include "BrowserSettings.h"
|
2017-12-12 10:15:23 +02:00
|
|
|
#include "core/Database.h"
|
|
|
|
#include "core/EntrySearcher.h"
|
2018-03-31 16:01:30 -04:00
|
|
|
#include "core/Group.h"
|
2017-12-12 10:15:23 +02:00
|
|
|
#include "core/Metadata.h"
|
|
|
|
#include "core/PasswordGenerator.h"
|
2018-01-25 14:21:05 +02:00
|
|
|
#include "core/Tools.h"
|
2018-01-15 01:15:39 +01:00
|
|
|
#include "gui/MainWindow.h"
|
2018-12-19 20:14:11 -08:00
|
|
|
#include "gui/MessageBox.h"
|
2018-01-25 14:21:05 +02:00
|
|
|
#ifdef Q_OS_MACOS
|
|
|
|
#include "gui/macutils/MacUtils.h"
|
|
|
|
#endif
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
const char BrowserService::KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
|
2018-11-01 13:07:51 +02:00
|
|
|
const char BrowserService::KEEPASSXCBROWSER_OLD_NAME[] = "keepassxc-browser Settings";
|
2018-10-24 17:49:53 +03:00
|
|
|
const char BrowserService::ASSOCIATE_KEY_PREFIX[] = "KPXC_BROWSER_";
|
2018-01-17 14:55:13 +02:00
|
|
|
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
|
2018-03-31 16:01:30 -04:00
|
|
|
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
2018-10-24 17:49:53 +03:00
|
|
|
// 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";
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
BrowserService::BrowserService(DatabaseTabWidget* parent)
|
|
|
|
: m_dbTabWidget(parent)
|
|
|
|
, m_dialogActive(false)
|
2018-05-07 23:22:59 -04:00
|
|
|
, m_bringToFrontRequested(false)
|
2019-02-01 18:11:15 -05:00
|
|
|
, m_prevWindowState(WindowState::Normal)
|
2019-01-30 16:48:22 +02:00
|
|
|
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2018-10-24 17:49:53 +03:00
|
|
|
// Don't connect the signals when used from DatabaseSettingsWidgetBrowser (parent is nullptr)
|
|
|
|
if (m_dbTabWidget) {
|
|
|
|
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
|
2018-11-01 04:27:38 +01:00
|
|
|
connect(
|
|
|
|
m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
|
2018-10-24 17:49:53 +03:00
|
|
|
connect(m_dbTabWidget,
|
|
|
|
SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
|
|
|
|
this,
|
|
|
|
SLOT(activateDatabaseChanged(DatabaseWidget*)));
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool BrowserService::isDatabaseOpened() const
|
|
|
|
{
|
|
|
|
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
|
|
|
if (!dbWidget) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-20 09:50:20 -05:00
|
|
|
return dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
|
|
|
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-01-17 14:55:13 +02:00
|
|
|
bool BrowserService::openDatabase(bool triggerUnlock)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2018-09-05 16:21:59 -04:00
|
|
|
if (!browserSettings()->unlockDatabase()) {
|
2017-12-12 10:15:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
|
|
|
if (!dbWidget) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-20 09:50:20 -05:00
|
|
|
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
|
|
|
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
|
2017-12-12 10:15:23 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-17 14:55:13 +02:00
|
|
|
if (triggerUnlock) {
|
2018-05-05 00:31:17 +03:00
|
|
|
m_bringToFrontRequested = true;
|
2018-01-25 14:21:05 +02:00
|
|
|
raiseWindow(true);
|
2018-01-17 14:55:13 +02:00
|
|
|
}
|
2018-01-15 01:15:39 +01:00
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::lockDatabase()
|
|
|
|
{
|
|
|
|
if (thread() != QThread::currentThread()) {
|
|
|
|
QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
|
|
|
|
}
|
|
|
|
|
|
|
|
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
|
|
|
if (!dbWidget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-20 09:50:20 -05:00
|
|
|
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
|
|
|
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
|
2017-12-12 10:15:23 +02:00
|
|
|
dbWidget->lock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString BrowserService::getDatabaseRootUuid()
|
|
|
|
{
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = getDatabase();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!db) {
|
2018-10-24 17:49:53 +03:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Group* rootGroup = db->rootGroup();
|
|
|
|
if (!rootGroup) {
|
2018-10-24 17:49:53 +03:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-09-30 08:45:06 -04:00
|
|
|
return rootGroup->uuidToHex();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString BrowserService::getDatabaseRecycleBinUuid()
|
|
|
|
{
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = getDatabase();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!db) {
|
2018-10-24 17:49:53 +03:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Group* recycleBin = db->metadata()->recycleBin();
|
|
|
|
if (!recycleBin) {
|
2018-10-24 17:49:53 +03:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2018-09-30 08:45:06 -04:00
|
|
|
return recycleBin->uuidToHex();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2019-03-16 10:34:50 +02:00
|
|
|
QJsonArray BrowserService::getChildrenFromGroup(Group* group)
|
2019-01-30 16:48:22 +02:00
|
|
|
{
|
|
|
|
QJsonArray groupList;
|
|
|
|
|
|
|
|
if (!group) {
|
|
|
|
return groupList;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& c : group->children()) {
|
|
|
|
if (c == group->database()->metadata()->recycleBin()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject jsonGroup;
|
|
|
|
jsonGroup["name"] = c->name();
|
|
|
|
jsonGroup["uuid"] = Tools::uuidToHex(c->uuid());
|
2019-03-16 10:34:50 +02:00
|
|
|
jsonGroup["children"] = getChildrenFromGroup(c);
|
2019-01-30 16:48:22 +02:00
|
|
|
groupList.push_back(jsonGroup);
|
|
|
|
}
|
|
|
|
return groupList;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject BrowserService::getDatabaseGroups()
|
|
|
|
{
|
|
|
|
auto db = getDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Group* rootGroup = db->rootGroup();
|
|
|
|
if (!rootGroup) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject root;
|
|
|
|
root["name"] = rootGroup->name();
|
|
|
|
root["uuid"] = Tools::uuidToHex(rootGroup->uuid());
|
2019-03-16 10:34:50 +02:00
|
|
|
root["children"] = getChildrenFromGroup(rootGroup);
|
2019-01-30 16:48:22 +02:00
|
|
|
|
|
|
|
QJsonArray groups;
|
|
|
|
groups.push_back(root);
|
|
|
|
|
|
|
|
QJsonObject result;
|
|
|
|
result["groups"] = groups;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-03-16 10:34:50 +02:00
|
|
|
QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
|
|
|
{
|
|
|
|
QJsonObject result;
|
|
|
|
if (thread() != QThread::currentThread()) {
|
2019-03-19 14:48:33 -04:00
|
|
|
QMetaObject::invokeMethod(this,
|
|
|
|
"createNewGroup",
|
|
|
|
Qt::BlockingQueuedConnection,
|
|
|
|
Q_RETURN_ARG(QJsonObject, result),
|
|
|
|
Q_ARG(QString, groupName));
|
2019-03-16 10:34:50 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto db = getDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Group* rootGroup = db->rootGroup();
|
|
|
|
if (!rootGroup) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto group = rootGroup->findGroupByPath(groupName);
|
|
|
|
|
|
|
|
// Group already exists
|
|
|
|
if (group) {
|
|
|
|
result["name"] = group->name();
|
|
|
|
result["uuid"] = Tools::uuidToHex(group->uuid());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto dialogResult = MessageBox::warning(nullptr,
|
|
|
|
tr("KeePassXC: Create a new group"),
|
|
|
|
tr("A request for creating a new group \"%1\" has been received.\n"
|
2019-03-19 14:48:33 -04:00
|
|
|
"Do you want to create this group?\n")
|
|
|
|
.arg(groupName),
|
2019-03-16 10:34:50 +02:00
|
|
|
MessageBox::Yes | MessageBox::No);
|
|
|
|
|
|
|
|
if (dialogResult != MessageBox::Yes) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString name, uuid;
|
|
|
|
Group* previousGroup = rootGroup;
|
|
|
|
auto groups = groupName.split("/");
|
|
|
|
|
|
|
|
// Returns the group name based on depth
|
|
|
|
auto getGroupName = [&](int depth) {
|
|
|
|
QString gName;
|
2019-03-19 14:48:33 -04:00
|
|
|
for (int i = 0; i < depth + 1; ++i) {
|
2019-03-16 10:34:50 +02:00
|
|
|
gName.append((i == 0 ? "" : "/") + groups[i]);
|
|
|
|
}
|
|
|
|
return gName;
|
|
|
|
};
|
2019-03-19 14:48:33 -04:00
|
|
|
|
2019-03-16 10:34:50 +02:00
|
|
|
// Create new group(s) always when the path is not found
|
|
|
|
for (int i = 0; i < groups.length(); ++i) {
|
|
|
|
QString gName = getGroupName(i);
|
|
|
|
auto tempGroup = rootGroup->findGroupByPath(gName);
|
|
|
|
if (!tempGroup) {
|
2019-03-19 14:48:33 -04:00
|
|
|
Group* newGroup = new Group();
|
2019-03-16 10:34:50 +02:00
|
|
|
newGroup->setName(groups[i]);
|
|
|
|
newGroup->setUuid(QUuid::createUuid());
|
|
|
|
newGroup->setParent(previousGroup);
|
|
|
|
name = newGroup->name();
|
|
|
|
uuid = Tools::uuidToHex(newGroup->uuid());
|
|
|
|
previousGroup = newGroup;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
previousGroup = tempGroup;
|
|
|
|
}
|
2019-03-19 14:48:33 -04:00
|
|
|
|
2019-03-16 10:34:50 +02:00
|
|
|
result["name"] = name;
|
|
|
|
result["uuid"] = uuid;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
QString BrowserService::storeKey(const QString& key)
|
|
|
|
{
|
|
|
|
QString id;
|
|
|
|
|
|
|
|
if (thread() != QThread::currentThread()) {
|
2018-03-31 16:01:30 -04:00
|
|
|
QMetaObject::invokeMethod(
|
2018-10-28 23:06:27 +01:00
|
|
|
this, "storeKey", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, id), Q_ARG(QString, key));
|
2017-12-12 10:15:23 +02:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = getDatabase();
|
2018-10-24 17:49:53 +03:00
|
|
|
if (!db) {
|
2018-01-15 00:09:15 +01:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-01-15 00:09:15 +01:00
|
|
|
bool contains;
|
2018-12-19 20:14:11 -08:00
|
|
|
MessageBox::Button dialogResult = MessageBox::Cancel;
|
2017-12-12 10:15:23 +02:00
|
|
|
|
|
|
|
do {
|
2018-01-15 01:15:39 +01:00
|
|
|
QInputDialog keyDialog;
|
2019-04-27 13:37:42 +03:00
|
|
|
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
|
2018-01-15 01:15:39 +01:00
|
|
|
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
|
|
|
keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
|
2018-03-31 16:01:30 -04:00
|
|
|
"If you would like to allow it access to your KeePassXC database,\n"
|
|
|
|
"give it a unique name to identify and accept it."));
|
2018-01-15 01:15:39 +01:00
|
|
|
keyDialog.setOkButtonText(tr("Save and allow access"));
|
2018-02-04 15:43:45 -06:00
|
|
|
keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
|
2018-01-25 14:21:05 +02:00
|
|
|
raiseWindow();
|
2018-01-15 01:15:39 +01:00
|
|
|
keyDialog.show();
|
|
|
|
keyDialog.activateWindow();
|
|
|
|
keyDialog.raise();
|
|
|
|
auto ok = keyDialog.exec();
|
|
|
|
|
|
|
|
id = keyDialog.textValue();
|
|
|
|
|
2019-04-27 13:37:42 +03:00
|
|
|
if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) {
|
2018-01-25 14:21:05 +02:00
|
|
|
hideWindow();
|
2018-01-15 00:09:15 +01:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
2018-01-15 00:09:15 +01:00
|
|
|
if (contains) {
|
2018-12-19 20:14:11 -08:00
|
|
|
dialogResult = MessageBox::warning(nullptr,
|
|
|
|
tr("KeePassXC: Overwrite existing key?"),
|
|
|
|
tr("A shared encryption key with the name \"%1\" "
|
|
|
|
"already exists.\nDo you want to overwrite it?")
|
|
|
|
.arg(id),
|
|
|
|
MessageBox::Overwrite | MessageBox::Cancel,
|
|
|
|
MessageBox::Cancel);
|
2018-01-15 00:09:15 +01:00
|
|
|
}
|
2018-12-19 20:14:11 -08:00
|
|
|
} while (contains && dialogResult == MessageBox::Cancel);
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-01-25 14:21:05 +02:00
|
|
|
hideWindow();
|
2018-10-24 17:49:53 +03:00
|
|
|
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key);
|
2017-12-12 10:15:23 +02:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString BrowserService::getKey(const QString& id)
|
|
|
|
{
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = getDatabase();
|
2018-10-24 17:49:53 +03:00
|
|
|
if (!db) {
|
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
return db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
|
|
|
const QString& url,
|
|
|
|
const QString& submitUrl,
|
2018-05-07 23:22:59 -04:00
|
|
|
const QString& realm,
|
2018-12-08 12:12:52 +02:00
|
|
|
const StringPairList& keyList,
|
|
|
|
const bool httpAuth)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
QJsonArray result;
|
|
|
|
if (thread() != QThread::currentThread()) {
|
2018-03-31 16:01:30 -04:00
|
|
|
QMetaObject::invokeMethod(this,
|
|
|
|
"findMatchingEntries",
|
|
|
|
Qt::BlockingQueuedConnection,
|
2017-12-12 10:15:23 +02:00
|
|
|
Q_RETURN_ARG(QJsonArray, result),
|
2018-10-28 23:06:27 +01:00
|
|
|
Q_ARG(QString, id),
|
|
|
|
Q_ARG(QString, url),
|
|
|
|
Q_ARG(QString, submitUrl),
|
|
|
|
Q_ARG(QString, realm),
|
2018-12-08 12:12:52 +02:00
|
|
|
Q_ARG(StringPairList, keyList),
|
|
|
|
Q_ARG(bool, httpAuth));
|
2017-12-12 10:15:23 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-09-05 16:21:59 -04:00
|
|
|
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
2018-12-08 12:12:52 +02:00
|
|
|
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
|
2017-12-12 10:15:23 +02:00
|
|
|
const QString host = QUrl(url).host();
|
|
|
|
const QString submitHost = QUrl(submitUrl).host();
|
|
|
|
|
|
|
|
// Check entries for authorization
|
|
|
|
QList<Entry*> pwEntriesToConfirm;
|
|
|
|
QList<Entry*> pwEntries;
|
2018-05-04 23:06:07 +03:00
|
|
|
for (Entry* entry : searchEntries(url, keyList)) {
|
2018-12-08 12:12:52 +02:00
|
|
|
// HTTP Basic Auth always needs a confirmation
|
|
|
|
if (!ignoreHttpAuth && httpAuth) {
|
|
|
|
pwEntriesToConfirm.append(entry);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
switch (checkAccess(entry, host, submitHost, realm)) {
|
|
|
|
case Denied:
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case Unknown:
|
|
|
|
if (alwaysAllowAccess) {
|
|
|
|
pwEntries.append(entry);
|
|
|
|
} else {
|
|
|
|
pwEntriesToConfirm.append(entry);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Allowed:
|
|
|
|
pwEntries.append(entry);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm entries
|
|
|
|
if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
|
|
|
|
pwEntries.append(pwEntriesToConfirm);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pwEntries.isEmpty()) {
|
|
|
|
return QJsonArray();
|
|
|
|
}
|
|
|
|
|
2019-04-27 13:37:42 +03:00
|
|
|
// Ensure that database is not locked when the popup was visible
|
|
|
|
if (!isDatabaseOpened()) {
|
|
|
|
return QJsonArray();
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
// Sort results
|
|
|
|
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
|
|
|
|
|
|
|
// Fill the list
|
|
|
|
for (Entry* entry : pwEntries) {
|
|
|
|
result << prepareEntry(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-01-20 09:50:20 -05:00
|
|
|
void BrowserService::addEntry(const QString& id,
|
|
|
|
const QString& login,
|
|
|
|
const QString& password,
|
|
|
|
const QString& url,
|
|
|
|
const QString& submitUrl,
|
|
|
|
const QString& realm,
|
2019-01-30 16:48:22 +02:00
|
|
|
const QString& group,
|
|
|
|
const QString& groupUuid,
|
2019-01-17 06:39:53 +01:00
|
|
|
const QSharedPointer<Database>& selectedDb)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2018-10-19 21:44:08 +03:00
|
|
|
if (thread() != QThread::currentThread()) {
|
|
|
|
QMetaObject::invokeMethod(this,
|
|
|
|
"addEntry",
|
|
|
|
Qt::BlockingQueuedConnection,
|
2018-10-28 23:06:27 +01:00
|
|
|
Q_ARG(QString, id),
|
|
|
|
Q_ARG(QString, login),
|
|
|
|
Q_ARG(QString, password),
|
|
|
|
Q_ARG(QString, url),
|
|
|
|
Q_ARG(QString, submitUrl),
|
|
|
|
Q_ARG(QString, realm),
|
2019-01-30 16:48:22 +02:00
|
|
|
Q_ARG(QString, group),
|
|
|
|
Q_ARG(QString, groupUuid),
|
2018-11-22 11:47:31 +01:00
|
|
|
Q_ARG(QSharedPointer<Database>, selectedDb));
|
2018-10-19 21:44:08 +03:00
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = selectedDb ? selectedDb : selectedDatabase();
|
2018-10-19 21:44:08 +03:00
|
|
|
if (!db) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
auto* entry = new Entry();
|
2018-03-23 11:18:06 +01:00
|
|
|
entry->setUuid(QUuid::createUuid());
|
2017-12-12 10:15:23 +02:00
|
|
|
entry->setTitle(QUrl(url).host());
|
|
|
|
entry->setUrl(url);
|
|
|
|
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
|
|
|
entry->setUsername(login);
|
|
|
|
entry->setPassword(password);
|
2019-01-30 16:48:22 +02:00
|
|
|
|
|
|
|
// Select a group for the entry
|
|
|
|
if (!group.isEmpty()) {
|
|
|
|
if (db->rootGroup()) {
|
|
|
|
auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid));
|
2019-05-07 10:51:24 +03:00
|
|
|
if (selectedGroup) {
|
2019-01-30 16:48:22 +02:00
|
|
|
entry->setGroup(selectedGroup);
|
2019-05-07 10:51:24 +03:00
|
|
|
} else {
|
|
|
|
entry->setGroup(getDefaultEntryGroup(db));
|
2019-01-30 16:48:22 +02:00
|
|
|
}
|
|
|
|
}
|
2019-05-07 10:51:24 +03:00
|
|
|
} else {
|
|
|
|
entry->setGroup(getDefaultEntryGroup(db));
|
2019-01-30 16:48:22 +02:00
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
|
|
|
|
const QString host = QUrl(url).host();
|
|
|
|
const QString submitHost = QUrl(submitUrl).host();
|
|
|
|
BrowserEntryConfig config;
|
|
|
|
config.allow(host);
|
|
|
|
|
|
|
|
if (!submitHost.isEmpty()) {
|
|
|
|
config.allow(submitHost);
|
|
|
|
}
|
|
|
|
if (!realm.isEmpty()) {
|
|
|
|
config.setRealm(realm);
|
|
|
|
}
|
|
|
|
config.save(entry);
|
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
void BrowserService::updateEntry(const QString& id,
|
|
|
|
const QString& uuid,
|
|
|
|
const QString& login,
|
|
|
|
const QString& password,
|
2018-10-19 21:44:08 +03:00
|
|
|
const QString& url,
|
|
|
|
const QString& submitUrl)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
if (thread() != QThread::currentThread()) {
|
2018-03-31 16:01:30 -04:00
|
|
|
QMetaObject::invokeMethod(this,
|
|
|
|
"updateEntry",
|
|
|
|
Qt::BlockingQueuedConnection,
|
2018-10-28 23:06:27 +01:00
|
|
|
Q_ARG(QString, id),
|
|
|
|
Q_ARG(QString, uuid),
|
|
|
|
Q_ARG(QString, login),
|
|
|
|
Q_ARG(QString, password),
|
|
|
|
Q_ARG(QString, url),
|
|
|
|
Q_ARG(QString, submitUrl));
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = selectedDatabase();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!db) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:48:22 +02:00
|
|
|
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!entry) {
|
2018-10-19 21:44:08 +03:00
|
|
|
// If entry is not found for update, add a new one to the selected database
|
2019-01-30 16:48:22 +02:00
|
|
|
addEntry(id, login, password, url, submitUrl, "", "", "", db);
|
2017-12-12 10:15:23 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-21 21:24:57 +02:00
|
|
|
// Check if the entry password is a reference. If so, update the original entry instead
|
|
|
|
while (entry->attributes()->isReference(EntryAttributes::PasswordKey)) {
|
|
|
|
const QUuid referenceUuid = entry->attributes()->referenceUuid(EntryAttributes::PasswordKey);
|
|
|
|
if (!referenceUuid.isNull()) {
|
|
|
|
entry = db->rootGroup()->findEntryByUuid(referenceUuid);
|
|
|
|
if (!entry) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
QString username = entry->username();
|
|
|
|
if (username.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-01 04:27:38 +01:00
|
|
|
if (username.compare(login, Qt::CaseSensitive) != 0
|
|
|
|
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
|
2018-12-19 20:14:11 -08:00
|
|
|
MessageBox::Button dialogResult = MessageBox::No;
|
2018-09-05 16:21:59 -04:00
|
|
|
if (!browserSettings()->alwaysAllowUpdate()) {
|
2018-01-25 14:21:05 +02:00
|
|
|
raiseWindow();
|
2019-01-20 09:50:20 -05:00
|
|
|
dialogResult = MessageBox::question(
|
|
|
|
nullptr,
|
|
|
|
tr("KeePassXC: Update Entry"),
|
|
|
|
tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host(), username),
|
|
|
|
MessageBox::Save | MessageBox::Cancel,
|
|
|
|
MessageBox::Cancel,
|
|
|
|
MessageBox::Raise);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-12-19 20:14:11 -08:00
|
|
|
if (browserSettings()->alwaysAllowUpdate() || dialogResult == MessageBox::Save) {
|
2017-12-12 10:15:23 +02:00
|
|
|
entry->beginUpdate();
|
2019-01-21 21:24:57 +02:00
|
|
|
if (!entry->attributes()->isReference(EntryAttributes::UserNameKey)) {
|
|
|
|
entry->setUsername(login);
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
entry->setPassword(password);
|
|
|
|
entry->endUpdate();
|
|
|
|
}
|
2018-01-25 14:21:05 +02:00
|
|
|
|
|
|
|
hideWindow();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-29 05:39:43 +01:00
|
|
|
QList<Entry*>
|
|
|
|
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& hostname, const QString& url)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
QList<Entry*> entries;
|
2018-11-22 11:47:31 +01:00
|
|
|
auto* rootGroup = db->rootGroup();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!rootGroup) {
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2018-03-25 16:24:30 -04:00
|
|
|
for (Entry* entry : EntrySearcher().search(baseDomain(hostname), rootGroup)) {
|
2018-07-11 11:53:37 +03:00
|
|
|
QString entryUrl = entry->url();
|
|
|
|
QUrl entryQUrl(entryUrl);
|
|
|
|
QString entryScheme = entryQUrl.scheme();
|
|
|
|
QUrl qUrl(url);
|
|
|
|
|
2018-11-01 04:27:38 +01:00
|
|
|
// 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)) {
|
2018-07-11 11:53:37 +03:00
|
|
|
continue;
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-06-16 08:44:08 +03:00
|
|
|
// Filter to match hostname in URL field
|
2018-11-01 04:27:38 +01:00
|
|
|
if ((!entryUrl.isEmpty() && hostname.contains(entryUrl))
|
2018-07-11 11:53:37 +03:00
|
|
|
|| (matchUrlScheme(entryUrl) && hostname.endsWith(entryQUrl.host()))) {
|
2018-11-01 04:27:38 +01:00
|
|
|
entries.append(entry);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2018-07-11 11:53:37 +03:00
|
|
|
QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPairList& keyList)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
// Get the list of databases to search
|
2018-11-22 11:47:31 +01:00
|
|
|
QList<QSharedPointer<Database>> databases;
|
2018-09-05 16:21:59 -04:00
|
|
|
if (browserSettings()->searchInAllDatabases()) {
|
2017-12-12 10:15:23 +02:00
|
|
|
const int count = m_dbTabWidget->count();
|
|
|
|
for (int i = 0; i < count; ++i) {
|
2018-11-22 11:47:31 +01:00
|
|
|
if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
|
|
|
if (const auto& db = dbWidget->database()) {
|
2019-01-20 09:50:20 -05:00
|
|
|
// Check if database is connected with KeePassXC-Browser
|
2018-10-27 23:23:34 +02:00
|
|
|
for (const StringPair& keyPair : keyList) {
|
2018-11-01 04:27:38 +01:00
|
|
|
QString key =
|
|
|
|
db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
|
2018-10-24 17:49:53 +03:00
|
|
|
if (!key.isEmpty() && keyPair.second == key) {
|
|
|
|
databases << db;
|
2018-05-04 23:06:07 +03:00
|
|
|
}
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-22 11:47:31 +01:00
|
|
|
} else if (const auto& db = getDatabase()) {
|
2017-12-12 10:15:23 +02:00
|
|
|
databases << db;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search entries matching the hostname
|
2018-07-11 11:53:37 +03:00
|
|
|
QString hostname = QUrl(url).host();
|
2017-12-12 10:15:23 +02:00
|
|
|
QList<Entry*> entries;
|
|
|
|
do {
|
2018-11-22 11:47:31 +01:00
|
|
|
for (const auto& db : databases) {
|
2018-07-11 11:53:37 +03:00
|
|
|
entries << searchEntries(db, hostname, url);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2019-01-17 06:39:53 +01:00
|
|
|
void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database>& currentDb)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = currentDb ? currentDb : getDatabase();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!db) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<Entry*> entries = db->rootGroup()->entriesRecursive();
|
2018-10-24 17:49:53 +03:00
|
|
|
QProgressDialog progress(tr("Converting attributes to custom data…"), tr("Abort"), 0, entries.count());
|
2017-12-12 10:15:23 +02:00
|
|
|
progress.setWindowModality(Qt::WindowModal);
|
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
int counter = 0;
|
|
|
|
int keyCounter = 0;
|
2017-12-12 10:15:23 +02:00
|
|
|
for (Entry* entry : entries) {
|
|
|
|
if (progress.wasCanceled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
if (moveSettingsToCustomData(entry, KEEPASSHTTP_NAME)) {
|
2017-12-12 10:15:23 +02:00
|
|
|
++counter;
|
|
|
|
}
|
2018-11-01 13:07:51 +02:00
|
|
|
|
|
|
|
if (moveSettingsToCustomData(entry, KEEPASSXCBROWSER_OLD_NAME)) {
|
|
|
|
++counter;
|
|
|
|
}
|
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
if (moveSettingsToCustomData(entry, KEEPASSXCBROWSER_NAME)) {
|
|
|
|
++counter;
|
|
|
|
}
|
|
|
|
|
2018-11-01 13:07:51 +02:00
|
|
|
if (entry->title() == KEEPASSHTTP_NAME || entry->title().contains(KEEPASSXCBROWSER_NAME, Qt::CaseInsensitive)) {
|
2018-10-24 17:49:53 +03:00
|
|
|
keyCounter += moveKeysToCustomData(entry, db);
|
|
|
|
delete entry;
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
progress.setValue(progress.value() + 1);
|
|
|
|
}
|
|
|
|
progress.reset();
|
|
|
|
|
|
|
|
if (counter > 0) {
|
2018-12-19 20:14:11 -08:00
|
|
|
MessageBox::information(nullptr,
|
|
|
|
tr("KeePassXC: Converted KeePassHTTP attributes"),
|
|
|
|
tr("Successfully converted attributes from %1 entry(s).\n"
|
|
|
|
"Moved %2 keys to custom data.",
|
|
|
|
"")
|
|
|
|
.arg(counter)
|
|
|
|
.arg(keyCounter),
|
|
|
|
MessageBox::Ok);
|
2018-10-24 17:49:53 +03:00
|
|
|
} else if (counter == 0 && keyCounter > 0) {
|
2018-12-19 20:14:11 -08:00
|
|
|
MessageBox::information(nullptr,
|
|
|
|
tr("KeePassXC: Converted KeePassHTTP attributes"),
|
|
|
|
tr("Successfully moved %n keys to custom data.", "", keyCounter),
|
|
|
|
MessageBox::Ok);
|
2017-12-12 10:15:23 +02:00
|
|
|
} else {
|
2018-12-19 20:14:11 -08:00
|
|
|
MessageBox::information(nullptr,
|
|
|
|
tr("KeePassXC: No entry with KeePassHTTP attributes found!"),
|
|
|
|
tr("The active database does not contain an entry with KeePassHTTP attributes."),
|
|
|
|
MessageBox::Ok);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2018-10-24 17:49:53 +03:00
|
|
|
|
|
|
|
// Rename password groupName
|
|
|
|
Group* rootGroup = db->rootGroup();
|
|
|
|
if (!rootGroup) {
|
|
|
|
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);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl)
|
|
|
|
{
|
|
|
|
QUrl url(entryUrl);
|
|
|
|
if (url.scheme().isEmpty()) {
|
2019-03-04 11:23:42 +02:00
|
|
|
url.setScheme("https");
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
|
2018-03-31 16:01:30 -04:00
|
|
|
const QString baseSubmitUrl =
|
|
|
|
url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-05-06 00:51:03 +03:00
|
|
|
// Build map of prioritized entries
|
|
|
|
QMultiMap<int, Entry*> priorities;
|
|
|
|
for (Entry* entry : pwEntries) {
|
2017-12-12 10:15:23 +02:00
|
|
|
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
|
|
|
|
}
|
|
|
|
|
2018-05-06 00:51:03 +03:00
|
|
|
QList<Entry*> results;
|
2018-09-05 16:21:59 -04:00
|
|
|
QString field = browserSettings()->sortByTitle() ? "Title" : "UserName";
|
2018-05-06 00:51:03 +03:00
|
|
|
for (int i = 100; i >= 0; i -= 5) {
|
|
|
|
if (priorities.count(i) > 0) {
|
|
|
|
// Sort same priority entries by Title or UserName
|
|
|
|
auto entries = priorities.values(i);
|
2018-09-05 16:21:59 -04:00
|
|
|
std::sort(entries.begin(), entries.end(), [&field](Entry* left, Entry* right) {
|
2018-11-01 04:27:38 +01:00
|
|
|
return (QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field))
|
|
|
|
< 0)
|
|
|
|
|| ((QString::localeAwareCompare(left->attributes()->value(field),
|
|
|
|
right->attributes()->value(field))
|
|
|
|
== 0)
|
|
|
|
&& (QString::localeAwareCompare(left->attributes()->value("UserName"),
|
|
|
|
right->attributes()->value("UserName"))
|
|
|
|
< 0));
|
2018-05-06 00:51:03 +03:00
|
|
|
});
|
|
|
|
results << entries;
|
2018-09-05 16:21:59 -04:00
|
|
|
if (browserSettings()->bestMatchOnly() && !pwEntries.isEmpty()) {
|
2018-05-06 00:51:03 +03:00
|
|
|
// Early out once we find the highest batch of matches
|
|
|
|
break;
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2018-05-06 00:51:03 +03:00
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-05-06 00:51:03 +03:00
|
|
|
return results;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
|
|
|
const QString& url,
|
|
|
|
const QString& host,
|
|
|
|
const QString& submitHost,
|
|
|
|
const QString& realm)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_dialogActive = true;
|
|
|
|
BrowserAccessControlDialog accessControlDialog;
|
2019-04-27 13:37:42 +03:00
|
|
|
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
|
2017-12-12 10:15:23 +02:00
|
|
|
accessControlDialog.setUrl(url);
|
|
|
|
accessControlDialog.setItems(pwEntriesToConfirm);
|
|
|
|
|
2018-01-25 14:21:05 +02:00
|
|
|
raiseWindow();
|
|
|
|
accessControlDialog.show();
|
|
|
|
accessControlDialog.activateWindow();
|
|
|
|
accessControlDialog.raise();
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
int res = accessControlDialog.exec();
|
|
|
|
if (accessControlDialog.remember()) {
|
|
|
|
for (Entry* entry : pwEntriesToConfirm) {
|
|
|
|
BrowserEntryConfig config;
|
|
|
|
config.load(entry);
|
|
|
|
if (res == QDialog::Accepted) {
|
|
|
|
config.allow(host);
|
|
|
|
if (!submitHost.isEmpty() && host != submitHost)
|
|
|
|
config.allow(submitHost);
|
|
|
|
} else if (res == QDialog::Rejected) {
|
|
|
|
config.deny(host);
|
|
|
|
if (!submitHost.isEmpty() && host != submitHost) {
|
|
|
|
config.deny(submitHost);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!realm.isEmpty()) {
|
|
|
|
config.setRealm(realm);
|
|
|
|
}
|
|
|
|
config.save(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_dialogActive = false;
|
2018-01-25 14:21:05 +02:00
|
|
|
hideWindow();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (res == QDialog::Accepted) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|
|
|
{
|
|
|
|
QJsonObject res;
|
|
|
|
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
|
|
|
|
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
|
|
|
|
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
|
2018-09-30 08:45:06 -04:00
|
|
|
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2018-04-16 11:41:45 +03:00
|
|
|
if (entry->hasTotp()) {
|
|
|
|
res["totp"] = entry->totp();
|
|
|
|
}
|
|
|
|
|
2019-05-09 08:46:16 +03:00
|
|
|
if (entry->isExpired()) {
|
|
|
|
res["expired"] = "true";
|
|
|
|
}
|
|
|
|
|
2018-09-05 16:21:59 -04:00
|
|
|
if (browserSettings()->supportKphFields()) {
|
2017-12-12 10:15:23 +02:00
|
|
|
const EntryAttributes* attr = entry->attributes();
|
|
|
|
QJsonArray stringFields;
|
|
|
|
for (const QString& key : attr->keys()) {
|
|
|
|
if (key.startsWith(QLatin1String("KPH: "))) {
|
|
|
|
QJsonObject sField;
|
|
|
|
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
|
|
|
stringFields << sField;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res["stringFields"] = stringFields;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
BrowserService::Access
|
2019-01-20 09:50:20 -05:00
|
|
|
BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
BrowserEntryConfig config;
|
|
|
|
if (!config.load(entry)) {
|
|
|
|
return Unknown;
|
|
|
|
}
|
2018-07-13 13:45:39 +03:00
|
|
|
if (entry->isExpired()) {
|
2019-05-09 08:46:16 +03:00
|
|
|
return browserSettings()->allowExpiredCredentials() ? Allowed : Denied;
|
2018-07-13 13:45:39 +03:00
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
|
|
|
|
return Allowed;
|
|
|
|
}
|
|
|
|
if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
|
|
|
|
return Denied;
|
|
|
|
}
|
|
|
|
if (!realm.isEmpty() && config.realm() != realm) {
|
|
|
|
return Denied;
|
|
|
|
}
|
|
|
|
return Unknown;
|
|
|
|
}
|
|
|
|
|
2019-05-07 10:51:24 +03:00
|
|
|
Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = selectedDb ? selectedDb : getDatabase();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!db) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:48:22 +02:00
|
|
|
auto* rootGroup = db->rootGroup();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!rootGroup) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
const QString groupName =
|
2019-05-07 10:51:24 +03:00
|
|
|
QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2019-01-30 16:48:22 +02:00
|
|
|
for (auto* g : rootGroup->groupsRecursive(true)) {
|
|
|
|
if (g->name() == groupName && !g->isRecycled()) {
|
2018-11-22 11:47:31 +01:00
|
|
|
return db->rootGroup()->findGroupByUuid(g->uuid());
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
auto* group = new Group();
|
2018-03-23 11:18:06 +01:00
|
|
|
group->setUuid(QUuid::createUuid());
|
2017-12-12 10:15:23 +02:00
|
|
|
group->setName(groupName);
|
|
|
|
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
|
|
|
group->setParent(rootGroup);
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
int BrowserService::sortPriority(const Entry* entry,
|
|
|
|
const QString& host,
|
|
|
|
const QString& submitUrl,
|
|
|
|
const QString& baseSubmitUrl) const
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
QUrl url(entry->url());
|
|
|
|
if (url.scheme().isEmpty()) {
|
|
|
|
url.setScheme("http");
|
|
|
|
}
|
|
|
|
const QString entryURL = url.toString(QUrl::StripTrailingSlash);
|
2018-03-31 16:01:30 -04:00
|
|
|
const QString baseEntryURL =
|
|
|
|
url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
2017-12-12 10:15:23 +02:00
|
|
|
|
|
|
|
if (submitUrl == entryURL) {
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
|
|
|
|
return 90;
|
|
|
|
}
|
|
|
|
if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
|
|
|
|
return 80;
|
|
|
|
}
|
|
|
|
if (entryURL == host) {
|
|
|
|
return 70;
|
|
|
|
}
|
|
|
|
if (entryURL == baseSubmitUrl) {
|
|
|
|
return 60;
|
|
|
|
}
|
|
|
|
if (entryURL.startsWith(submitUrl)) {
|
|
|
|
return 50;
|
|
|
|
}
|
|
|
|
if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
|
|
|
|
return 40;
|
|
|
|
}
|
|
|
|
if (submitUrl.startsWith(entryURL)) {
|
|
|
|
return 30;
|
|
|
|
}
|
|
|
|
if (submitUrl.startsWith(baseEntryURL)) {
|
|
|
|
return 20;
|
|
|
|
}
|
|
|
|
if (entryURL.startsWith(host)) {
|
|
|
|
return 10;
|
|
|
|
}
|
|
|
|
if (host.startsWith(entryURL)) {
|
|
|
|
return 5;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BrowserService::matchUrlScheme(const QString& url)
|
|
|
|
{
|
|
|
|
QUrl address(url);
|
|
|
|
return !address.scheme().isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BrowserService::removeFirstDomain(QString& hostname)
|
|
|
|
{
|
|
|
|
int pos = hostname.indexOf(".");
|
|
|
|
if (pos < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't remove the second-level domain if it's the only one
|
|
|
|
if (hostname.count(".") > 1) {
|
|
|
|
hostname = hostname.mid(pos + 1);
|
|
|
|
return !hostname.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Nothing removed
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-08-30 13:40:41 +03:00
|
|
|
/**
|
|
|
|
* Gets the base domain of URL.
|
|
|
|
*
|
|
|
|
* Returns the base domain, e.g. https://another.example.co.uk -> example.co.uk
|
|
|
|
*/
|
|
|
|
QString BrowserService::baseDomain(const QString& url) const
|
|
|
|
{
|
|
|
|
QUrl qurl = QUrl::fromUserInput(url);
|
|
|
|
QString hostname = qurl.host();
|
|
|
|
|
|
|
|
if (hostname.isEmpty() || !hostname.contains(qurl.topLevelDomain())) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the top level domain part from the hostname, e.g. https://another.example.co.uk -> https://another.example
|
|
|
|
hostname.chop(qurl.topLevelDomain().length());
|
|
|
|
// Split the URL and select the last part, e.g. https://another.example -> example
|
|
|
|
QString baseDomain = hostname.split('.').last();
|
|
|
|
// Append the top level domain back to the URL, e.g. example -> example.co.uk
|
|
|
|
baseDomain.append(qurl.topLevelDomain());
|
|
|
|
return baseDomain;
|
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
QSharedPointer<Database> BrowserService::getDatabase()
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
|
|
|
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
2018-11-22 11:47:31 +01:00
|
|
|
if (const auto& db = dbWidget->database()) {
|
2017-12-12 10:15:23 +02:00
|
|
|
return db;
|
|
|
|
}
|
|
|
|
}
|
2018-11-22 11:47:31 +01:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
QSharedPointer<Database> BrowserService::selectedDatabase()
|
2018-10-19 21:44:08 +03:00
|
|
|
{
|
|
|
|
QList<DatabaseWidget*> databaseWidgets;
|
2019-01-20 09:50:20 -05:00
|
|
|
for (int i = 0;; ++i) {
|
2018-11-22 11:47:31 +01:00
|
|
|
auto* dbWidget = m_dbTabWidget->databaseWidgetFromIndex(i);
|
2018-10-19 21:44:08 +03:00
|
|
|
// Add only open databases
|
2019-01-20 09:50:20 -05:00
|
|
|
if (dbWidget && dbWidget->database()->hasKey()
|
|
|
|
&& (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
|
|
|
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode)) {
|
2018-11-22 11:47:31 +01:00
|
|
|
databaseWidgets.push_back(dbWidget);
|
2018-10-19 21:44:08 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Break out if dbStruct.dbWidget is nullptr
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
BrowserEntrySaveDialog browserEntrySaveDialog;
|
|
|
|
int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_dbTabWidget->currentDatabaseWidget());
|
|
|
|
if (openDatabaseCount > 1) {
|
|
|
|
int res = browserEntrySaveDialog.exec();
|
|
|
|
if (res == QDialog::Accepted) {
|
|
|
|
const auto selectedDatabase = browserEntrySaveDialog.getSelected();
|
|
|
|
if (selectedDatabase.length() > 0) {
|
|
|
|
int index = selectedDatabase[0]->data(Qt::UserRole).toUInt();
|
|
|
|
return databaseWidgets[index]->database();
|
|
|
|
}
|
|
|
|
} else {
|
2018-11-22 11:47:31 +01:00
|
|
|
return {};
|
2018-10-19 21:44:08 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return current database
|
|
|
|
return getDatabase();
|
|
|
|
}
|
|
|
|
|
2018-10-24 17:49:53 +03:00
|
|
|
bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name) const
|
|
|
|
{
|
|
|
|
if (entry->attributes()->contains(name)) {
|
|
|
|
QString attr = entry->attributes()->value(name);
|
|
|
|
entry->beginUpdate();
|
|
|
|
if (!attr.isEmpty()) {
|
|
|
|
entry->customData()->set(KEEPASSXCBROWSER_NAME, attr);
|
|
|
|
}
|
|
|
|
entry->attributes()->remove(name);
|
|
|
|
entry->endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-17 06:39:53 +01:00
|
|
|
int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Database>& db) const
|
2018-10-24 17:49:53 +03:00
|
|
|
{
|
|
|
|
int keyCounter = 0;
|
|
|
|
for (const auto& key : entry->attributes()->keys()) {
|
|
|
|
if (key.contains(LEGACY_ASSOCIATE_KEY_PREFIX)) {
|
|
|
|
QString publicKey = key;
|
|
|
|
publicKey.remove(LEGACY_ASSOCIATE_KEY_PREFIX);
|
|
|
|
|
|
|
|
// Add key to database custom data
|
|
|
|
if (db && !db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey)) {
|
2018-11-01 04:27:38 +01:00
|
|
|
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey,
|
|
|
|
entry->attributes()->value(key));
|
2018-10-24 17:49:53 +03:00
|
|
|
++keyCounter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return keyCounter;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BrowserService::checkLegacySettings()
|
|
|
|
{
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = getDatabase();
|
2018-10-24 17:49:53 +03:00
|
|
|
if (!db) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool legacySettingsFound = false;
|
|
|
|
QList<Entry*> entries = db->rootGroup()->entriesRecursive();
|
|
|
|
for (const auto& e : entries) {
|
2019-01-20 09:50:20 -05:00
|
|
|
if ((e->attributes()->contains(KEEPASSHTTP_NAME) || e->attributes()->contains(KEEPASSXCBROWSER_NAME))
|
|
|
|
|| (e->title() == KEEPASSHTTP_NAME || e->title().contains(KEEPASSXCBROWSER_NAME, Qt::CaseInsensitive))) {
|
2018-10-24 17:49:53 +03:00
|
|
|
legacySettingsFound = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!legacySettingsFound) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-19 14:48:33 -04:00
|
|
|
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);
|
2018-12-19 20:14:11 -08:00
|
|
|
|
|
|
|
return dialogResult == MessageBox::Yes;
|
2018-10-24 17:49:53 +03:00
|
|
|
}
|
|
|
|
|
2018-01-25 14:21:05 +02:00
|
|
|
void BrowserService::hideWindow() const
|
|
|
|
{
|
2019-02-01 18:11:15 -05:00
|
|
|
if (m_prevWindowState == WindowState::Minimized) {
|
2018-01-25 14:21:05 +02:00
|
|
|
getMainWindow()->showMinimized();
|
|
|
|
} else {
|
|
|
|
#ifdef Q_OS_MACOS
|
2019-02-01 18:11:15 -05:00
|
|
|
if (m_prevWindowState == WindowState::Hidden) {
|
2019-01-16 18:03:54 +02:00
|
|
|
macUtils()->hideOwnWindow();
|
|
|
|
} else {
|
|
|
|
macUtils()->raiseLastActiveWindow();
|
|
|
|
}
|
2018-01-25 14:21:05 +02:00
|
|
|
#else
|
2019-02-01 18:11:15 -05:00
|
|
|
if (m_prevWindowState == WindowState::Hidden) {
|
|
|
|
getMainWindow()->hideWindow();
|
|
|
|
} else {
|
|
|
|
getMainWindow()->lower();
|
|
|
|
}
|
2018-01-25 14:21:05 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::raiseWindow(const bool force)
|
|
|
|
{
|
2019-02-01 18:11:15 -05:00
|
|
|
m_prevWindowState = WindowState::Normal;
|
|
|
|
if (getMainWindow()->isMinimized()) {
|
|
|
|
m_prevWindowState = WindowState::Minimized;
|
|
|
|
}
|
2018-01-25 14:21:05 +02:00
|
|
|
#ifdef Q_OS_MACOS
|
2019-03-19 08:30:49 -04:00
|
|
|
Q_UNUSED(force);
|
|
|
|
|
2019-02-01 18:11:15 -05:00
|
|
|
if (macUtils()->isHidden()) {
|
|
|
|
m_prevWindowState = WindowState::Hidden;
|
|
|
|
}
|
2018-01-25 14:21:05 +02:00
|
|
|
macUtils()->raiseOwnWindow();
|
|
|
|
Tools::wait(500);
|
|
|
|
#else
|
2019-02-01 18:11:15 -05:00
|
|
|
if (getMainWindow()->isHidden()) {
|
|
|
|
m_prevWindowState = WindowState::Hidden;
|
|
|
|
}
|
|
|
|
|
2018-01-25 14:21:05 +02:00
|
|
|
if (force) {
|
|
|
|
getMainWindow()->bringToFront();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
|
|
|
{
|
|
|
|
if (dbWidget) {
|
|
|
|
emit databaseLocked();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
|
|
|
|
{
|
|
|
|
if (dbWidget) {
|
2018-05-05 00:31:17 +03:00
|
|
|
if (m_bringToFrontRequested) {
|
2018-01-25 14:21:05 +02:00
|
|
|
hideWindow();
|
2018-05-05 00:31:17 +03:00
|
|
|
m_bringToFrontRequested = false;
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
emit databaseUnlocked();
|
2018-10-24 17:49:53 +03:00
|
|
|
|
|
|
|
if (checkLegacySettings()) {
|
|
|
|
convertAttributesToCustomData();
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
|
|
|
|
{
|
|
|
|
if (dbWidget) {
|
|
|
|
auto currentMode = dbWidget->currentMode();
|
2018-11-22 11:47:31 +01:00
|
|
|
if (currentMode == DatabaseWidget::Mode::ViewMode || currentMode == DatabaseWidget::Mode::EditMode) {
|
2017-12-12 10:15:23 +02:00
|
|
|
emit databaseUnlocked();
|
|
|
|
} else {
|
|
|
|
emit databaseLocked();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|