2017-12-12 10:15:23 +02:00
|
|
|
/*
|
2023-02-25 21:19:34 +02:00
|
|
|
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
2023-01-29 17:32:24 +02:00
|
|
|
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
|
|
|
* Copyright (C) 2013 Francois Ferrand
|
2018-11-01 04:27:38 +01:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
2021-07-11 22:10:29 -04:00
|
|
|
#include "BrowserService.h"
|
2020-05-10 21:20:00 -04:00
|
|
|
#include "BrowserAction.h"
|
2018-03-31 16:01:30 -04:00
|
|
|
#include "BrowserEntryConfig.h"
|
2018-10-19 21:44:08 +03:00
|
|
|
#include "BrowserEntrySaveDialog.h"
|
2020-05-10 21:20:00 -04:00
|
|
|
#include "BrowserHost.h"
|
2022-02-23 00:52:51 +02:00
|
|
|
#include "BrowserMessageBuilder.h"
|
2018-03-31 16:01:30 -04:00
|
|
|
#include "BrowserSettings.h"
|
2018-01-25 14:21:05 +02:00
|
|
|
#include "core/Tools.h"
|
2023-10-14 16:18:27 +03:00
|
|
|
#include "core/UrlTools.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"
|
2021-10-10 14:49:25 +03:00
|
|
|
#include "gui/osutils/OSUtils.h"
|
2023-10-25 17:12:55 +03:00
|
|
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
|
|
|
#include "BrowserPasskeys.h"
|
|
|
|
#include "BrowserPasskeysConfirmationDialog.h"
|
|
|
|
#endif
|
2018-01-25 14:21:05 +02:00
|
|
|
#ifdef Q_OS_MACOS
|
2020-01-28 21:42:57 +01:00
|
|
|
#include "gui/osutils/macutils/MacUtils.h"
|
2018-01-25 14:21:05 +02:00
|
|
|
#endif
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2021-07-11 22:10:29 -04:00
|
|
|
#include <QCheckBox>
|
|
|
|
#include <QCryptographicHash>
|
|
|
|
#include <QHostAddress>
|
|
|
|
#include <QInputDialog>
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QListWidget>
|
2022-02-23 00:52:51 +02:00
|
|
|
#include <QLocalSocket>
|
2021-07-11 22:10:29 -04:00
|
|
|
#include <QProgressDialog>
|
|
|
|
#include <QUrl>
|
|
|
|
|
2019-08-15 12:35:11 +03:00
|
|
|
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
|
|
|
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
|
|
|
static const QString KEEPASSXCBROWSER_GROUP_NAME = QStringLiteral("KeePassXC-Browser Passwords");
|
2018-03-31 16:01:30 -04:00
|
|
|
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
2023-10-25 17:12:55 +03:00
|
|
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
|
|
|
static int KEEPASSXCBROWSER_PASSKEY_ICON = 13;
|
|
|
|
#endif
|
2018-10-24 17:49:53 +03:00
|
|
|
// These are for the settings and password conversion
|
2019-08-15 12:35:11 +03:00
|
|
|
static const QString KEEPASSHTTP_NAME = QStringLiteral("KeePassHttp Settings");
|
|
|
|
static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwords");
|
2019-08-15 12:35:11 +03:00
|
|
|
// Extra entry related options saved in custom data
|
2019-08-15 12:35:11 +03:00
|
|
|
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
|
|
|
|
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
|
2019-11-24 18:10:40 +02:00
|
|
|
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
|
2020-09-06 14:53:51 +02:00
|
|
|
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
|
2022-06-05 10:37:00 +03:00
|
|
|
const QString BrowserService::OPTION_OMIT_WWW = QStringLiteral("BrowserOmitWww");
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
|
|
|
|
|
|
|
BrowserService::BrowserService()
|
|
|
|
: QObject()
|
|
|
|
, m_browserHost(new BrowserHost)
|
2022-10-26 10:24:49 +03:00
|
|
|
, m_dialogActive(false)
|
2020-07-05 14:32:23 +03: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
|
|
|
{
|
2020-05-10 21:20:00 -04:00
|
|
|
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
|
2021-02-15 17:28:16 -05:00
|
|
|
connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked);
|
|
|
|
connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked);
|
|
|
|
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
setEnabled(browserSettings()->isEnabled());
|
|
|
|
}
|
|
|
|
|
|
|
|
BrowserService* BrowserService::instance()
|
|
|
|
{
|
|
|
|
return s_browserService;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::setEnabled(bool enabled)
|
|
|
|
{
|
|
|
|
if (enabled) {
|
|
|
|
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
|
|
|
|
if (browserSettings()->updateBinaryPath()) {
|
|
|
|
browserSettings()->updateBinaryPaths();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_browserHost->start();
|
|
|
|
} else {
|
|
|
|
m_browserHost->stop();
|
2018-10-24 17:49:53 +03:00
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool BrowserService::isDatabaseOpened() const
|
|
|
|
{
|
2020-05-10 21:20:00 -04:00
|
|
|
if (m_currentDatabaseWidget) {
|
|
|
|
return !m_currentDatabaseWidget->isLocked();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2020-05-10 21:20:00 -04:00
|
|
|
return false;
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
if (m_currentDatabaseWidget && !m_currentDatabaseWidget->isLocked()) {
|
2017-12-12 10:15:23 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-24 06:08:41 +03:00
|
|
|
if (triggerUnlock && !m_bringToFrontRequested) {
|
2020-07-05 14:32:23 +03:00
|
|
|
m_bringToFrontRequested = true;
|
|
|
|
updateWindowState();
|
2019-10-28 10:52:03 +02:00
|
|
|
emit requestUnlock();
|
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()
|
|
|
|
{
|
2020-05-10 21:20:00 -04:00
|
|
|
if (m_currentDatabaseWidget) {
|
|
|
|
m_currentDatabaseWidget->lock();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2020-05-10 21:20:00 -04:00
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
QString BrowserService::getDatabaseHash(bool legacy)
|
|
|
|
{
|
|
|
|
if (legacy) {
|
|
|
|
return QCryptographicHash::hash(
|
|
|
|
(browserService()->getDatabaseRootUuid() + browserService()->getDatabaseRecycleBinUuid()).toUtf8(),
|
|
|
|
QCryptographicHash::Sha256)
|
|
|
|
.toHex();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2020-05-10 21:20:00 -04:00
|
|
|
return QCryptographicHash::hash(getDatabaseRootUuid().toUtf8(), QCryptographicHash::Sha256).toHex();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
QJsonObject BrowserService::getDatabaseGroups()
|
2019-01-30 16:48:22 +02:00
|
|
|
{
|
2020-05-10 21:20:00 -04:00
|
|
|
auto db = getDatabase();
|
2019-01-30 16:48:22 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-02-25 20:09:36 +01:00
|
|
|
QJsonArray BrowserService::getDatabaseEntries()
|
|
|
|
{
|
|
|
|
auto db = getDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Group* rootGroup = db->rootGroup();
|
|
|
|
if (!rootGroup) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonArray entries;
|
|
|
|
for (const auto& group : rootGroup->groupsRecursive(true)) {
|
|
|
|
if (group == db->metadata()->recycleBin()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& entry : group->entries()) {
|
|
|
|
QJsonObject jentry;
|
|
|
|
jentry["title"] = entry->resolveMultiplePlaceholders(entry->title());
|
|
|
|
jentry["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
|
|
|
jentry["url"] = entry->resolveMultiplePlaceholders(entry->url());
|
|
|
|
entries.push_back(jentry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2019-03-16 10:34:50 +02:00
|
|
|
QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
|
|
|
{
|
|
|
|
auto db = getDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Group* rootGroup = db->rootGroup();
|
|
|
|
if (!rootGroup) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto group = rootGroup->findGroupByPath(groupName);
|
|
|
|
|
|
|
|
// Group already exists
|
|
|
|
if (group) {
|
2020-05-10 21:20:00 -04:00
|
|
|
QJsonObject result;
|
2019-03-16 10:34:50 +02:00
|
|
|
result["name"] = group->name();
|
|
|
|
result["uuid"] = Tools::uuidToHex(group->uuid());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-07-20 09:52:20 +03:00
|
|
|
auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
|
2019-03-16 10:34:50 +02:00
|
|
|
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) {
|
2020-05-10 21:20:00 -04:00
|
|
|
return {};
|
2019-03-16 10:34:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
QJsonObject result;
|
2019-03-16 10:34:50 +02:00
|
|
|
result["name"] = name;
|
|
|
|
result["uuid"] = uuid;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-08-17 13:17:58 +03:00
|
|
|
QString BrowserService::getCurrentTotp(const QString& uuid)
|
|
|
|
{
|
|
|
|
QList<QSharedPointer<Database>> databases;
|
|
|
|
if (browserSettings()->searchInAllDatabases()) {
|
|
|
|
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
|
|
|
auto db = dbWidget->database();
|
|
|
|
if (db) {
|
|
|
|
databases << db;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
databases << getDatabase();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto entryUuid = Tools::hexToUuid(uuid);
|
|
|
|
for (const auto& db : databases) {
|
|
|
|
auto entry = db->rootGroup()->findEntryByUuid(entryUuid, true);
|
|
|
|
if (entry) {
|
|
|
|
return entry->totp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
QJsonArray
|
2023-02-25 21:19:34 +02:00
|
|
|
BrowserService::findEntries(const EntryParameters& entryParameters, const StringPairList& keyList, bool* entriesFound)
|
2022-04-24 10:39:12 +03:00
|
|
|
{
|
2023-02-25 21:19:34 +02:00
|
|
|
if (entriesFound) {
|
|
|
|
*entriesFound = false;
|
|
|
|
}
|
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
|
|
|
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
|
2023-02-18 22:52:31 +02:00
|
|
|
const QString siteHost = QUrl(entryParameters.siteUrl).host();
|
|
|
|
const QString formHost = QUrl(entryParameters.formUrl).host();
|
2022-04-24 10:39:12 +03:00
|
|
|
|
|
|
|
// Check entries for authorization
|
2023-02-25 21:19:34 +02:00
|
|
|
QList<Entry*> entriesToConfirm;
|
|
|
|
QList<Entry*> allowedEntries;
|
2023-02-18 22:52:31 +02:00
|
|
|
for (auto* entry : searchEntries(entryParameters.siteUrl, entryParameters.formUrl, keyList)) {
|
2022-04-24 10:39:12 +03:00
|
|
|
auto entryCustomData = entry->customData();
|
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
if (!entryParameters.httpAuth
|
2022-04-24 10:39:12 +03:00
|
|
|
&& ((entryCustomData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)
|
|
|
|
&& entryCustomData->value(BrowserService::OPTION_ONLY_HTTP_AUTH) == TRUE_STR)
|
|
|
|
|| entry->group()->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Enable)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
if (entryParameters.httpAuth
|
2022-04-24 10:39:12 +03:00
|
|
|
&& ((entryCustomData->contains(BrowserService::OPTION_NOT_HTTP_AUTH)
|
|
|
|
&& entryCustomData->value(BrowserService::OPTION_NOT_HTTP_AUTH) == TRUE_STR)
|
|
|
|
|| entry->group()->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Enable)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTP Basic Auth always needs a confirmation
|
2023-02-25 21:19:34 +02:00
|
|
|
if (!ignoreHttpAuth && entryParameters.httpAuth) {
|
|
|
|
entriesToConfirm.append(entry);
|
2022-04-24 10:39:12 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
switch (checkAccess(entry, siteHost, formHost, entryParameters.realm)) {
|
2022-04-24 10:39:12 +03:00
|
|
|
case Denied:
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case Unknown:
|
|
|
|
if (alwaysAllowAccess) {
|
2023-02-25 21:19:34 +02:00
|
|
|
allowedEntries.append(entry);
|
2022-04-24 10:39:12 +03:00
|
|
|
} else {
|
2023-02-25 21:19:34 +02:00
|
|
|
entriesToConfirm.append(entry);
|
2022-04-24 10:39:12 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Allowed:
|
2023-02-25 21:19:34 +02:00
|
|
|
allowedEntries.append(entry);
|
2022-04-24 10:39:12 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
if (entriesToConfirm.isEmpty() && allowedEntries.isEmpty()) {
|
|
|
|
return {};
|
2022-04-24 10:39:12 +03:00
|
|
|
}
|
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
// Confirm entries
|
|
|
|
auto selectedEntriesToConfirm =
|
|
|
|
confirmEntries(entriesToConfirm, entryParameters, siteHost, formHost, entryParameters.httpAuth);
|
|
|
|
if (!selectedEntriesToConfirm.isEmpty()) {
|
|
|
|
allowedEntries.append(selectedEntriesToConfirm);
|
2022-04-24 10:39:12 +03:00
|
|
|
}
|
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
// Ensure that database is not locked when the popup was visible
|
|
|
|
if (!isDatabaseOpened()) {
|
|
|
|
return {};
|
|
|
|
}
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
// Sort results
|
2023-02-25 21:19:34 +02:00
|
|
|
allowedEntries = sortEntries(allowedEntries, entryParameters.siteUrl, entryParameters.formUrl);
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
// Fill the list
|
2023-02-25 21:19:34 +02:00
|
|
|
QJsonArray entries;
|
|
|
|
for (auto* entry : allowedEntries) {
|
|
|
|
entries.append(prepareEntry(entry));
|
2022-10-26 10:24:49 +03:00
|
|
|
}
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
if (entriesFound != nullptr) {
|
|
|
|
*entriesFound = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries;
|
2022-10-26 10:24:49 +03:00
|
|
|
}
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& entriesToConfirm,
|
2023-02-18 22:52:31 +02:00
|
|
|
const EntryParameters& entryParameters,
|
2022-10-26 10:24:49 +03:00
|
|
|
const QString& siteHost,
|
|
|
|
const QString& formUrl,
|
|
|
|
const bool httpAuth)
|
|
|
|
{
|
2023-02-25 21:19:34 +02:00
|
|
|
if (entriesToConfirm.isEmpty() || m_dialogActive) {
|
2022-10-26 10:24:49 +03:00
|
|
|
return {};
|
2022-04-24 10:39:12 +03:00
|
|
|
}
|
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
m_dialogActive = true;
|
2022-04-24 10:39:12 +03:00
|
|
|
updateWindowState();
|
2023-07-20 09:52:20 +03:00
|
|
|
BrowserAccessControlDialog accessControlDialog(m_currentDatabaseWidget);
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &accessControlDialog, SLOT(reject()));
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) {
|
2023-02-25 21:19:34 +02:00
|
|
|
auto entry = entriesToConfirm[item->row()];
|
2023-02-18 22:52:31 +02:00
|
|
|
denyEntry(entry, siteHost, formUrl, entryParameters.realm);
|
2022-10-26 10:24:49 +03:00
|
|
|
});
|
|
|
|
|
2023-07-09 21:33:05 +03:00
|
|
|
accessControlDialog.setEntries(entriesToConfirm, entryParameters.siteUrl, httpAuth);
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
QList<Entry*> allowedEntries;
|
|
|
|
auto ret = accessControlDialog.exec();
|
2023-07-09 21:33:05 +03:00
|
|
|
auto remember = accessControlDialog.remember();
|
|
|
|
|
|
|
|
// All are denied
|
|
|
|
if (ret == QDialog::Rejected && remember) {
|
|
|
|
for (auto& entry : entriesToConfirm) {
|
|
|
|
denyEntry(entry, siteHost, formUrl, entryParameters.realm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Some/all are accepted
|
|
|
|
if (ret == QDialog::Accepted) {
|
|
|
|
auto selectedEntries = accessControlDialog.getEntries(SelectionType::Selected);
|
|
|
|
for (auto& item : selectedEntries) {
|
|
|
|
auto entry = entriesToConfirm[item->row()];
|
|
|
|
allowedEntries.append(entry);
|
|
|
|
|
|
|
|
if (remember) {
|
2023-02-18 22:52:31 +02:00
|
|
|
allowEntry(entry, siteHost, formUrl, entryParameters.realm);
|
2022-10-26 10:24:49 +03:00
|
|
|
}
|
2023-02-25 21:19:34 +02:00
|
|
|
}
|
|
|
|
|
2023-07-09 21:33:05 +03:00
|
|
|
// Remembered non-selected entries must be denied
|
|
|
|
if (remember) {
|
|
|
|
auto nonSelectedEntries = accessControlDialog.getEntries(SelectionType::NonSelected);
|
|
|
|
for (auto& item : nonSelectedEntries) {
|
|
|
|
auto entry = entriesToConfirm[item->row()];
|
|
|
|
denyEntry(entry, siteHost, formUrl, entryParameters.realm);
|
|
|
|
}
|
2022-10-26 10:24:49 +03:00
|
|
|
}
|
|
|
|
}
|
2022-04-24 10:39:12 +03:00
|
|
|
|
2023-07-09 21:33:05 +03:00
|
|
|
// Handle disabled entries (returned Accept/Reject status does not matter)
|
|
|
|
auto disabledEntries = accessControlDialog.getEntries(SelectionType::Disabled);
|
|
|
|
for (auto& item : disabledEntries) {
|
|
|
|
auto entry = entriesToConfirm[item->row()];
|
|
|
|
denyEntry(entry, siteHost, formUrl, entryParameters.realm);
|
|
|
|
}
|
|
|
|
|
2022-10-26 10:24:49 +03:00
|
|
|
// Re-hide the application if it wasn't visible before
|
2022-04-24 10:39:12 +03:00
|
|
|
hideWindow();
|
2022-10-26 10:24:49 +03:00
|
|
|
m_dialogActive = false;
|
|
|
|
|
|
|
|
return allowedEntries;
|
2022-04-24 10:39:12 +03:00
|
|
|
}
|
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
2021-10-17 09:53:25 +03:00
|
|
|
{
|
|
|
|
if (!m_passwordGenerator) {
|
2023-08-15 07:52:48 -04:00
|
|
|
m_passwordGenerator = PasswordGeneratorWidget::popupGenerator();
|
2021-10-17 09:53:25 +03:00
|
|
|
|
|
|
|
connect(m_passwordGenerator.data(), &PasswordGeneratorWidget::closed, m_passwordGenerator.data(), [=] {
|
2022-01-30 17:59:29 +02:00
|
|
|
if (!m_passwordGenerator->isPasswordGenerated()) {
|
2022-02-23 00:52:51 +02:00
|
|
|
auto errorMessage = browserMessageBuilder()->getErrorReply("generate-password",
|
|
|
|
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
2023-02-18 22:52:31 +02:00
|
|
|
m_browserHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
2022-01-30 17:59:29 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 07:52:48 -04:00
|
|
|
QTimer::singleShot(50, this, [&] { hideWindow(); });
|
2021-10-17 09:53:25 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
connect(m_passwordGenerator.data(),
|
|
|
|
&PasswordGeneratorWidget::appliedPassword,
|
|
|
|
m_passwordGenerator.data(),
|
2022-02-23 00:52:51 +02:00
|
|
|
[=](const QString& password) {
|
2023-02-18 22:52:31 +02:00
|
|
|
const Parameters params{{"password", password}};
|
|
|
|
m_browserHost->sendClientMessage(keyPairMessage.socket,
|
|
|
|
browserMessageBuilder()->buildResponse("generate-password",
|
|
|
|
keyPairMessage.nonce,
|
|
|
|
params,
|
|
|
|
keyPairMessage.publicKey,
|
|
|
|
keyPairMessage.secretKey));
|
2022-02-23 00:52:51 +02:00
|
|
|
});
|
2021-10-17 09:53:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
raiseWindow();
|
2023-08-15 07:52:48 -04:00
|
|
|
m_passwordGenerator->show();
|
2021-10-17 09:53:25 +03:00
|
|
|
m_passwordGenerator->raise();
|
|
|
|
m_passwordGenerator->activateWindow();
|
|
|
|
}
|
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
bool BrowserService::isPasswordGeneratorRequested() const
|
2021-10-17 09:53:25 +03:00
|
|
|
{
|
2023-08-15 07:52:48 -04:00
|
|
|
return m_passwordGenerator && m_passwordGenerator->isVisible();
|
2021-10-17 09:53:25 +03:00
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
QString BrowserService::storeKey(const QString& key)
|
|
|
|
{
|
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;
|
2020-05-10 21:20:00 -04:00
|
|
|
auto dialogResult = MessageBox::Cancel;
|
|
|
|
QString id;
|
2017-12-12 10:15:23 +02:00
|
|
|
|
|
|
|
do {
|
2023-07-20 09:52:20 +03:00
|
|
|
QInputDialog keyDialog(m_currentDatabaseWidget);
|
2020-12-12 12:08:02 -05:00
|
|
|
connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &keyDialog, SLOT(reject()));
|
2018-01-15 01:15:39 +01:00
|
|
|
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
2019-10-19 14:22:44 +03:00
|
|
|
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()));
|
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
|
|
|
}
|
|
|
|
|
2020-05-01 11:07:14 +03:00
|
|
|
contains = db->metadata()->customData()->contains(CustomData::BrowserKeyPrefix + id);
|
2018-01-15 00:09:15 +01:00
|
|
|
if (contains) {
|
2023-07-20 09:52:20 +03:00
|
|
|
dialogResult = MessageBox::warning(m_currentDatabaseWidget,
|
2018-12-19 20:14:11 -08:00
|
|
|
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();
|
2020-05-01 11:07:14 +03:00
|
|
|
db->metadata()->customData()->set(CustomData::BrowserKeyPrefix + id, key);
|
2020-03-02 10:18:33 +02:00
|
|
|
db->metadata()->customData()->set(QString("%1_%2").arg(CustomData::Created, id),
|
|
|
|
Clock::currentDateTime().toString(Qt::SystemLocaleShortDate));
|
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
|
|
|
}
|
|
|
|
|
2020-05-01 11:07:14 +03:00
|
|
|
return db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + id);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
|
|
|
// Passkey registration
|
|
|
|
QJsonObject BrowserService::showPasskeysRegisterPrompt(const QJsonObject& publicKey,
|
|
|
|
const QString& origin,
|
|
|
|
const StringPairList& keyList)
|
|
|
|
{
|
|
|
|
auto db = selectedDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return getPasskeyError(ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto userJson = publicKey["user"].toObject();
|
|
|
|
const auto username = userJson["name"].toString();
|
|
|
|
const auto userHandle = userJson["id"].toString();
|
|
|
|
const auto rpId = publicKey["rp"]["id"].toString();
|
|
|
|
const auto rpName = publicKey["rp"]["name"].toString();
|
|
|
|
const auto timeoutValue = publicKey["timeout"].toInt();
|
|
|
|
const auto excludeCredentials = publicKey["excludeCredentials"].toArray();
|
|
|
|
const auto attestation = publicKey["attestation"].toString();
|
|
|
|
|
|
|
|
// Only support these two for now
|
|
|
|
if (attestation != BrowserPasskeys::PASSKEYS_ATTESTATION_NONE
|
|
|
|
&& attestation != BrowserPasskeys::PASSKEYS_ATTESTATION_DIRECT) {
|
|
|
|
return getPasskeyError(ERROR_PASSKEYS_ATTESTATION_NOT_SUPPORTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto authenticatorSelection = publicKey["authenticatorSelection"].toObject();
|
|
|
|
const auto userVerification = authenticatorSelection["userVerification"].toString();
|
|
|
|
if (!browserPasskeys()->isUserVerificationValid(userVerification)) {
|
|
|
|
return getPasskeyError(ERROR_PASSKEYS_INVALID_USER_VERIFICATION);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!excludeCredentials.isEmpty() && isPasskeyCredentialExcluded(excludeCredentials, origin, keyList)) {
|
|
|
|
return getPasskeyError(ERROR_PASSKEYS_CREDENTIAL_IS_EXCLUDED);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto existingEntries = getPasskeyEntries(rpId, keyList);
|
|
|
|
const auto timeout = browserPasskeys()->getTimeout(userVerification, timeoutValue);
|
|
|
|
|
|
|
|
raiseWindow();
|
|
|
|
BrowserPasskeysConfirmationDialog confirmDialog;
|
|
|
|
confirmDialog.registerCredential(username, rpId, existingEntries, timeout);
|
|
|
|
|
|
|
|
auto dialogResult = confirmDialog.exec();
|
|
|
|
if (dialogResult == QDialog::Accepted) {
|
|
|
|
const auto publicKeyCredentials = browserPasskeys()->buildRegisterPublicKeyCredential(publicKey, origin);
|
|
|
|
|
|
|
|
if (confirmDialog.isPasskeyUpdated()) {
|
|
|
|
addPasskeyToEntry(confirmDialog.getSelectedEntry(),
|
|
|
|
rpId,
|
|
|
|
rpName,
|
|
|
|
username,
|
2023-11-08 07:19:13 +02:00
|
|
|
publicKeyCredentials.credentialId,
|
2023-10-25 17:12:55 +03:00
|
|
|
userHandle,
|
|
|
|
publicKeyCredentials.key);
|
|
|
|
} else {
|
2023-11-08 07:19:13 +02:00
|
|
|
addPasskeyToGroup(nullptr,
|
|
|
|
origin,
|
|
|
|
rpId,
|
|
|
|
rpName,
|
|
|
|
username,
|
|
|
|
publicKeyCredentials.credentialId,
|
|
|
|
userHandle,
|
|
|
|
publicKeyCredentials.key);
|
2023-10-25 17:12:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
hideWindow();
|
|
|
|
return publicKeyCredentials.response;
|
|
|
|
}
|
|
|
|
|
|
|
|
hideWindow();
|
|
|
|
return getPasskeyError(ERROR_PASSKEYS_REQUEST_CANCELED);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Passkey authentication
|
|
|
|
QJsonObject BrowserService::showPasskeysAuthenticationPrompt(const QJsonObject& publicKey,
|
|
|
|
const QString& origin,
|
|
|
|
const StringPairList& keyList)
|
|
|
|
{
|
|
|
|
auto db = selectedDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return getPasskeyError(ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto userVerification = publicKey["userVerification"].toString();
|
|
|
|
if (!browserPasskeys()->isUserVerificationValid(userVerification)) {
|
|
|
|
return getPasskeyError(ERROR_PASSKEYS_INVALID_USER_VERIFICATION);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse "allowCredentials"
|
|
|
|
const auto rpId = publicKey["rpId"].toString();
|
|
|
|
const auto entries = getPasskeyAllowedEntries(publicKey, rpId, keyList);
|
|
|
|
if (entries.isEmpty()) {
|
|
|
|
return getPasskeyError(ERROR_KEEPASS_NO_LOGINS_FOUND);
|
|
|
|
}
|
|
|
|
|
|
|
|
// With single entry, if no verification is needed, return directly
|
|
|
|
if (entries.count() == 1 && userVerification == BrowserPasskeys::REQUIREMENT_DISCOURAGED) {
|
|
|
|
return getPublicKeyCredentialFromEntry(entries.first(), publicKey, origin);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto timeout = publicKey["timeout"].toInt();
|
|
|
|
|
|
|
|
raiseWindow();
|
|
|
|
BrowserPasskeysConfirmationDialog confirmDialog;
|
|
|
|
confirmDialog.authenticateCredential(entries, origin, timeout);
|
|
|
|
auto dialogResult = confirmDialog.exec();
|
|
|
|
if (dialogResult == QDialog::Accepted) {
|
|
|
|
hideWindow();
|
|
|
|
const auto selectedEntry = confirmDialog.getSelectedEntry();
|
|
|
|
return getPublicKeyCredentialFromEntry(selectedEntry, publicKey, origin);
|
|
|
|
}
|
|
|
|
|
|
|
|
hideWindow();
|
|
|
|
return getPasskeyError(ERROR_PASSKEYS_REQUEST_CANCELED);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::addPasskeyToGroup(Group* group,
|
|
|
|
const QString& url,
|
|
|
|
const QString& rpId,
|
|
|
|
const QString& rpName,
|
|
|
|
const QString& username,
|
2023-11-08 07:19:13 +02:00
|
|
|
const QString& credentialId,
|
2023-10-25 17:12:55 +03:00
|
|
|
const QString& userHandle,
|
|
|
|
const QString& privateKey)
|
|
|
|
{
|
|
|
|
// If no group provided, use the default browser group of the selected database
|
|
|
|
if (!group) {
|
|
|
|
auto db = selectedDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
group = getDefaultEntryGroup(db);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* entry = new Entry();
|
|
|
|
entry->setUuid(QUuid::createUuid());
|
|
|
|
entry->setGroup(group);
|
|
|
|
entry->setTitle(tr("%1 (Passkey)").arg(rpName));
|
|
|
|
entry->setUsername(username);
|
|
|
|
entry->setUrl(url);
|
|
|
|
entry->setIcon(KEEPASSXCBROWSER_PASSKEY_ICON);
|
|
|
|
|
2023-11-08 07:19:13 +02:00
|
|
|
addPasskeyToEntry(entry, rpId, rpName, username, credentialId, userHandle, privateKey);
|
2023-10-25 17:12:55 +03:00
|
|
|
|
|
|
|
// Remove blank entry history
|
|
|
|
entry->removeHistoryItems(entry->historyItems());
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::addPasskeyToEntry(Entry* entry,
|
|
|
|
const QString& rpId,
|
|
|
|
const QString& rpName,
|
|
|
|
const QString& username,
|
2023-11-08 07:19:13 +02:00
|
|
|
const QString& credentialId,
|
2023-10-25 17:12:55 +03:00
|
|
|
const QString& userHandle,
|
|
|
|
const QString& privateKey)
|
|
|
|
{
|
|
|
|
// Reserved for future use
|
|
|
|
Q_UNUSED(rpName)
|
|
|
|
|
|
|
|
Q_ASSERT(entry);
|
|
|
|
if (!entry) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-23 06:11:25 +02:00
|
|
|
// Ask confirmation if entry already contains a Passkey
|
|
|
|
if (entry->hasPasskey()) {
|
|
|
|
if (MessageBox::question(
|
|
|
|
m_currentDatabaseWidget,
|
|
|
|
tr("KeePassXC: Update Passkey"),
|
|
|
|
tr("Entry already has a Passkey.\nDo you want to overwrite the Passkey in %1 - %2?")
|
|
|
|
.arg(entry->title(), entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME)),
|
|
|
|
MessageBox::Overwrite | MessageBox::Cancel,
|
|
|
|
MessageBox::Cancel)
|
|
|
|
!= MessageBox::Overwrite) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
entry->beginUpdate();
|
|
|
|
|
|
|
|
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
|
2023-11-08 07:19:13 +02:00
|
|
|
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID, credentialId, true);
|
2023-10-25 17:12:55 +03:00
|
|
|
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
|
|
|
|
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY, rpId);
|
|
|
|
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE, userHandle, true);
|
|
|
|
|
|
|
|
entry->endUpdate();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
void BrowserService::addEntry(const EntryParameters& entryParameters,
|
2019-01-30 16:48:22 +02:00
|
|
|
const QString& group,
|
|
|
|
const QString& groupUuid,
|
2021-11-25 19:47:45 +02:00
|
|
|
const bool downloadFavicon,
|
2019-01-17 06:39:53 +01:00
|
|
|
const QSharedPointer<Database>& selectedDb)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2020-05-10 21:20:00 -04:00
|
|
|
// TODO: select database based on this key id
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = selectedDb ? selectedDb : selectedDatabase();
|
2018-10-19 21:44:08 +03:00
|
|
|
if (!db) {
|
2019-06-18 18:23:12 -04:00
|
|
|
return;
|
2018-10-19 21:44:08 +03:00
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
auto* entry = new Entry();
|
2018-03-23 11:18:06 +01:00
|
|
|
entry->setUuid(QUuid::createUuid());
|
2023-10-25 17:12:55 +03:00
|
|
|
entry->setTitle(entryParameters.title.isEmpty() ? QUrl(entryParameters.siteUrl).host() : entryParameters.title);
|
2023-02-18 22:52:31 +02:00
|
|
|
entry->setUrl(entryParameters.siteUrl);
|
2017-12-12 10:15:23 +02:00
|
|
|
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
2023-02-18 22:52:31 +02:00
|
|
|
entry->setUsername(entryParameters.login);
|
|
|
|
entry->setPassword(entryParameters.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
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
const QString host = QUrl(entryParameters.siteUrl).host();
|
|
|
|
const QString submitHost = QUrl(entryParameters.formUrl).host();
|
2017-12-12 10:15:23 +02:00
|
|
|
BrowserEntryConfig config;
|
|
|
|
config.allow(host);
|
|
|
|
|
|
|
|
if (!submitHost.isEmpty()) {
|
|
|
|
config.allow(submitHost);
|
|
|
|
}
|
2023-02-18 22:52:31 +02:00
|
|
|
if (!entryParameters.realm.isEmpty()) {
|
|
|
|
config.setRealm(entryParameters.realm);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
config.save(entry);
|
2021-11-25 19:47:45 +02:00
|
|
|
|
|
|
|
if (downloadFavicon && m_currentDatabaseWidget) {
|
|
|
|
m_currentDatabaseWidget->downloadFaviconInBackground(entry);
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
bool BrowserService::updateEntry(const EntryParameters& entryParameters, const QString& uuid)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2020-05-10 21:20:00 -04:00
|
|
|
// TODO: select database based on this key id
|
2018-11-22 11:47:31 +01:00
|
|
|
auto db = selectedDatabase();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (!db) {
|
2020-05-10 21:20:00 -04:00
|
|
|
return false;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
auto 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
|
2023-02-18 22:52:31 +02:00
|
|
|
addEntry(entryParameters, "", "", false, db);
|
2020-05-10 21:20:00 -04:00
|
|
|
return true;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
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) {
|
2020-05-10 21:20:00 -04:00
|
|
|
return false;
|
2019-01-21 21:24:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-18 22:52:31 +02:00
|
|
|
auto username = entry->username();
|
2017-12-12 10:15:23 +02:00
|
|
|
if (username.isEmpty()) {
|
2020-05-10 21:20:00 -04:00
|
|
|
return false;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
bool result = false;
|
2023-02-18 22:52:31 +02:00
|
|
|
if (username.compare(entryParameters.login, Qt::CaseSensitive) != 0
|
|
|
|
|| entry->password().compare(entryParameters.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();
|
2023-07-20 09:52:20 +03:00
|
|
|
dialogResult = MessageBox::question(m_currentDatabaseWidget,
|
2023-02-18 22:52:31 +02:00
|
|
|
tr("KeePassXC: Update Entry"),
|
|
|
|
tr("Do you want to update the information in %1 - %2?")
|
|
|
|
.arg(QUrl(entryParameters.siteUrl).host(), username),
|
|
|
|
MessageBox::Save | MessageBox::Cancel,
|
2023-09-18 10:20:58 +03:00
|
|
|
MessageBox::Cancel);
|
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)) {
|
2023-02-18 22:52:31 +02:00
|
|
|
entry->setUsername(entryParameters.login);
|
2019-01-21 21:24:57 +02:00
|
|
|
}
|
2023-02-18 22:52:31 +02:00
|
|
|
entry->setPassword(entryParameters.password);
|
2017-12-12 10:15:23 +02:00
|
|
|
entry->endUpdate();
|
2020-05-10 21:20:00 -04:00
|
|
|
result = true;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2018-01-25 14:21:05 +02:00
|
|
|
|
|
|
|
hideWindow();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2019-06-13 10:05:29 +03:00
|
|
|
|
|
|
|
return result;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2021-09-06 18:38:25 +03:00
|
|
|
bool BrowserService::deleteEntry(const QString& uuid)
|
|
|
|
{
|
|
|
|
auto db = selectedDatabase();
|
|
|
|
if (!db) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
|
|
|
|
if (!entry) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-20 09:52:20 +03:00
|
|
|
auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
|
2021-09-06 18:38:25 +03:00
|
|
|
tr("KeePassXC: Delete entry"),
|
|
|
|
tr("A request for deleting entry \"%1\" has been received.\n"
|
|
|
|
"Do you want to delete the entry?\n")
|
|
|
|
.arg(entry->title()),
|
|
|
|
MessageBox::Yes | MessageBox::No);
|
|
|
|
if (dialogResult != MessageBox::Yes) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
db->recycleEntry(entry);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
QList<Entry*> BrowserService::searchEntries(const QSharedPointer<Database>& db,
|
|
|
|
const QString& siteUrl,
|
|
|
|
const QString& formUrl,
|
|
|
|
bool passkey)
|
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;
|
|
|
|
}
|
|
|
|
|
2019-08-15 12:35:11 +03:00
|
|
|
for (const auto& group : rootGroup->groupsRecursive(true)) {
|
2021-10-09 14:46:39 -04:00
|
|
|
if (group->isRecycled()
|
|
|
|
|| group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY) == Group::Enable) {
|
2018-07-11 11:53:37 +03:00
|
|
|
continue;
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2022-06-05 10:37:00 +03:00
|
|
|
const auto omitWwwSubdomain =
|
|
|
|
group->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW) == Group::Enable;
|
|
|
|
|
2019-08-15 12:35:11 +03:00
|
|
|
for (auto* entry : group->entries()) {
|
2021-10-09 14:46:39 -04:00
|
|
|
if (entry->isRecycled()
|
|
|
|
|| (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
|
|
|
|
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR)) {
|
2019-08-15 12:35:11 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
if (!passkey && !shouldIncludeEntry(entry, siteUrl, formUrl, omitWwwSubdomain)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
|
|
|
// With Passkeys, check for the Relying Party instead of URL
|
|
|
|
if (passkey && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) != siteUrl) {
|
2019-08-15 12:35:11 +03:00
|
|
|
continue;
|
|
|
|
}
|
2023-10-25 17:12:55 +03:00
|
|
|
#endif
|
2019-08-15 12:35:11 +03:00
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
// Additional URL check may have already inserted the entry to the list
|
|
|
|
if (!entries.contains(entry)) {
|
|
|
|
entries.append(entry);
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
QList<Entry*> BrowserService::searchEntries(const QString& siteUrl,
|
|
|
|
const QString& formUrl,
|
|
|
|
const StringPairList& keyList,
|
|
|
|
bool passkey)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2019-08-27 21:50:54 +03:00
|
|
|
// Check if database is connected with KeePassXC-Browser
|
|
|
|
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
|
|
|
for (const StringPair& keyPair : keyList) {
|
2020-05-01 11:07:14 +03:00
|
|
|
QString key = db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + keyPair.first);
|
2019-08-27 21:50:54 +03:00
|
|
|
if (!key.isEmpty() && keyPair.second == key) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
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()) {
|
2020-05-10 21:20:00 -04:00
|
|
|
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
|
|
|
auto db = dbWidget->database();
|
|
|
|
if (db && databaseConnected(dbWidget->database())) {
|
|
|
|
databases << db;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-10 21:20:00 -04:00
|
|
|
} else {
|
|
|
|
const auto& db = getDatabase();
|
2019-08-27 21:50:54 +03:00
|
|
|
if (databaseConnected(db)) {
|
|
|
|
databases << db;
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Search entries matching the hostname
|
2022-06-05 10:37:00 +03:00
|
|
|
QString hostname = QUrl(siteUrl).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) {
|
2023-10-25 17:12:55 +03:00
|
|
|
entries << searchEntries(db, siteUrl, formUrl, passkey);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2021-10-10 14:49:25 +03:00
|
|
|
void BrowserService::requestGlobalAutoType(const QString& search)
|
|
|
|
{
|
|
|
|
emit osUtils->globalShortcutTriggered("autotype", search);
|
|
|
|
}
|
|
|
|
|
2023-02-25 21:19:34 +02:00
|
|
|
QList<Entry*> BrowserService::sortEntries(QList<Entry*>& entries, const QString& siteUrl, const QString& formUrl)
|
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;
|
2023-02-25 21:19:34 +02:00
|
|
|
for (auto* entry : entries) {
|
2022-11-12 11:27:28 +02:00
|
|
|
priorities.insert(sortPriority(entry->getAllUrls(), siteUrl, formUrl), entry);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
auto keys = priorities.uniqueKeys();
|
|
|
|
std::sort(keys.begin(), keys.end(), [](int l, int r) { return l > r; });
|
|
|
|
|
2018-05-06 00:51:03 +03:00
|
|
|
QList<Entry*> results;
|
2020-09-13 17:38:19 +03:00
|
|
|
for (auto key : keys) {
|
2021-04-01 06:14:29 +03:00
|
|
|
results << priorities.values(key);
|
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
if (browserSettings()->bestMatchOnly() && !results.isEmpty()) {
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
void BrowserService::allowEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2022-04-24 10:39:12 +03:00
|
|
|
BrowserEntryConfig config;
|
|
|
|
config.load(entry);
|
|
|
|
config.allow(siteHost);
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
if (!formUrl.isEmpty() && siteHost != formUrl) {
|
|
|
|
config.allow(formUrl);
|
|
|
|
}
|
2020-01-05 12:07:18 -05:00
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
if (!realm.isEmpty()) {
|
|
|
|
config.setRealm(realm);
|
|
|
|
}
|
2020-04-24 18:38:33 +03:00
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
config.save(entry);
|
|
|
|
}
|
2020-01-05 12:07:18 -05:00
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
void BrowserService::denyEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm)
|
|
|
|
{
|
|
|
|
BrowserEntryConfig config;
|
|
|
|
config.load(entry);
|
|
|
|
config.deny(siteHost);
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
if (!formUrl.isEmpty() && siteHost != formUrl) {
|
|
|
|
config.deny(formUrl);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
if (!realm.isEmpty()) {
|
|
|
|
config.setRealm(realm);
|
|
|
|
}
|
2020-04-24 18:38:33 +03:00
|
|
|
|
2022-04-24 10:39:12 +03:00
|
|
|
config.save(entry);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|
|
|
{
|
|
|
|
QJsonObject res;
|
2023-11-23 06:11:25 +02:00
|
|
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
|
|
|
// Use Passkey's username instead if found
|
|
|
|
res["login"] = entry->hasPasskey() ? entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME)
|
|
|
|
: entry->resolveMultiplePlaceholders(entry->username());
|
|
|
|
#else
|
2017-12-12 10:15:23 +02:00
|
|
|
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
|
2023-11-23 06:11:25 +02:00
|
|
|
#endif
|
2017-12-12 10:15:23 +02:00
|
|
|
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());
|
2020-01-03 22:58:17 +01:00
|
|
|
res["group"] = entry->resolveMultiplePlaceholders(entry->group()->name());
|
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()) {
|
2019-11-24 18:10:40 +02:00
|
|
|
res["expired"] = TRUE_STR;
|
2019-05-09 08:46:16 +03:00
|
|
|
}
|
|
|
|
|
2021-10-09 14:46:39 -04:00
|
|
|
auto skipAutoSubmitGroup = entry->group()->resolveCustomDataTriState(BrowserService::OPTION_SKIP_AUTO_SUBMIT);
|
|
|
|
if (skipAutoSubmitGroup == Group::Inherit) {
|
|
|
|
if (entry->customData()->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
|
|
|
|
res["skipAutoSubmit"] = entry->customData()->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
res["skipAutoSubmit"] = skipAutoSubmitGroup == Group::Enable ? TRUE_STR : FALSE_STR;
|
2019-08-15 12:35:11 +03:00
|
|
|
}
|
|
|
|
|
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;
|
2019-08-15 12:35:11 +03:00
|
|
|
for (const auto& key : attr->keys()) {
|
|
|
|
if (key.startsWith("KPH: ")) {
|
2017-12-12 10:15:23 +02:00
|
|
|
QJsonObject sField;
|
|
|
|
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
2019-05-19 15:58:52 -04:00
|
|
|
stringFields.append(sField);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
res["stringFields"] = stringFields;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2018-03-31 16:01:30 -04:00
|
|
|
BrowserService::Access
|
2020-09-13 17:38:19 +03:00
|
|
|
BrowserService::checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2023-06-29 18:13:34 +03:00
|
|
|
if (entry->isExpired() && !browserSettings()->allowExpiredCredentials()) {
|
|
|
|
return Denied;
|
2020-06-19 18:46:49 -04:00
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
BrowserEntryConfig config;
|
|
|
|
if (!config.load(entry)) {
|
|
|
|
return Unknown;
|
|
|
|
}
|
2020-09-13 17:38:19 +03:00
|
|
|
if ((config.isAllowed(siteHost)) && (formHost.isEmpty() || config.isAllowed(formHost))) {
|
2017-12-12 10:15:23 +02:00
|
|
|
return Allowed;
|
|
|
|
}
|
2020-09-13 17:38:19 +03:00
|
|
|
if ((config.isDenied(siteHost)) || (!formHost.isEmpty() && config.isDenied(formHost))) {
|
2017-12-12 10:15:23 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:48:22 +02:00
|
|
|
for (auto* g : rootGroup->groupsRecursive(true)) {
|
2019-08-15 12:35:11 +03:00
|
|
|
if (g->name() == KEEPASSXCBROWSER_GROUP_NAME && !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());
|
2019-08-15 12:35:11 +03:00
|
|
|
group->setName(KEEPASSXCBROWSER_GROUP_NAME);
|
2017-12-12 10:15:23 +02:00
|
|
|
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
|
|
|
group->setParent(rootGroup);
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
// Returns the maximum sort priority given a set of match urls and the
|
|
|
|
// extension provided site and form url.
|
2022-06-05 10:37:00 +03:00
|
|
|
int BrowserService::sortPriority(const QStringList& urls, const QString& siteUrl, const QString& formUrl)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2020-09-13 17:38:19 +03:00
|
|
|
QList<int> priorityList;
|
|
|
|
// NOTE: QUrl::matches is utterly broken in Qt < 5.11, so we work around that
|
|
|
|
// by removing parts of the url that we don't match and direct matching others
|
|
|
|
const auto stdOpts = QUrl::RemoveFragment | QUrl::RemoveUserInfo;
|
2022-06-05 10:37:00 +03:00
|
|
|
const auto adjustedSiteUrl = QUrl(siteUrl).adjusted(stdOpts);
|
|
|
|
const auto adjustedFormUrl = QUrl(formUrl).adjusted(stdOpts);
|
2020-09-13 17:38:19 +03:00
|
|
|
|
|
|
|
auto getPriority = [&](const QString& givenUrl) {
|
|
|
|
auto url = QUrl::fromUserInput(givenUrl).adjusted(stdOpts);
|
|
|
|
|
|
|
|
// Default to https scheme if undefined
|
|
|
|
if (url.scheme().isEmpty() || !givenUrl.contains("://")) {
|
|
|
|
url.setScheme("https");
|
|
|
|
}
|
2020-01-11 12:50:21 +02:00
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
// Add the empty path to the URL if it's missing.
|
|
|
|
// URL's from the extension always have a path set, entry URL's can be without.
|
|
|
|
if (url.path().isEmpty() && !url.hasFragment() && !url.hasQuery()) {
|
|
|
|
url.setPath("/");
|
|
|
|
}
|
2020-01-11 12:50:21 +02:00
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
// Reject invalid urls and hosts, except 'localhost', and scheme mismatch
|
|
|
|
if (!url.isValid() || (!url.host().contains(".") && url.host() != "localhost")
|
2022-06-05 10:37:00 +03:00
|
|
|
|| url.scheme() != adjustedSiteUrl.scheme()) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 0;
|
|
|
|
}
|
2017-12-12 10:15:23 +02:00
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
// Exact match with site url or form url
|
2022-06-05 10:37:00 +03:00
|
|
|
if (url.matches(adjustedSiteUrl, QUrl::None) || url.matches(adjustedFormUrl, QUrl::None)) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exact match without the query string
|
2022-06-05 10:37:00 +03:00
|
|
|
if (url.matches(adjustedSiteUrl, QUrl::RemoveQuery) || url.matches(adjustedFormUrl, QUrl::RemoveQuery)) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 90;
|
|
|
|
}
|
|
|
|
|
2021-09-04 23:41:37 +02:00
|
|
|
// Parent directory match
|
2022-06-05 10:37:00 +03:00
|
|
|
if (url.isParentOf(adjustedSiteUrl) || url.isParentOf(adjustedFormUrl)) {
|
2021-09-04 23:41:37 +02:00
|
|
|
return 85;
|
|
|
|
}
|
|
|
|
|
2020-09-13 17:38:19 +03:00
|
|
|
// Match without path (ie, FQDN match), form url prioritizes lower than site url
|
2022-06-05 10:37:00 +03:00
|
|
|
if (url.host() == adjustedSiteUrl.host()) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 80;
|
|
|
|
}
|
2022-06-05 10:37:00 +03:00
|
|
|
if (url.host() == adjustedFormUrl.host()) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 70;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Site/form url ends with given url (subdomain mismatch)
|
2022-06-05 10:37:00 +03:00
|
|
|
if (adjustedSiteUrl.host().endsWith(url.host())) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 60;
|
|
|
|
}
|
2022-06-05 10:37:00 +03:00
|
|
|
if (adjustedFormUrl.host().endsWith(url.host())) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return 50;
|
|
|
|
}
|
|
|
|
|
|
|
|
// No valid match found
|
2019-11-01 20:13:12 +02:00
|
|
|
return 0;
|
2020-09-13 17:38:19 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
for (const auto& entryUrl : urls) {
|
|
|
|
priorityList << getPriority(entryUrl);
|
2019-11-01 20:13:12 +02:00
|
|
|
}
|
2020-09-13 17:38:19 +03:00
|
|
|
|
|
|
|
return *std::max_element(priorityList.begin(), priorityList.end());
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-20 14:53:42 +02:00
|
|
|
/* Test if a search URL matches a custom entry. If the URL has the schema "keepassxc", some special checks will be made.
|
|
|
|
* Otherwise, this simply delegates to handleURL(). */
|
2022-06-05 10:37:00 +03:00
|
|
|
bool BrowserService::shouldIncludeEntry(Entry* entry,
|
|
|
|
const QString& url,
|
|
|
|
const QString& submitUrl,
|
|
|
|
const bool omitWwwSubdomain)
|
2020-05-20 14:53:42 +02:00
|
|
|
{
|
|
|
|
// Use this special scheme to find entries by UUID
|
2020-10-17 10:05:02 -04:00
|
|
|
if (url.startsWith("keepassxc://by-uuid/")) {
|
|
|
|
return url.endsWith("by-uuid/" + entry->uuidToHex());
|
|
|
|
} else if (url.startsWith("keepassxc://by-path/")) {
|
|
|
|
return url.endsWith("by-path/" + entry->path());
|
2020-05-20 14:53:42 +02:00
|
|
|
}
|
2022-06-05 10:37:00 +03:00
|
|
|
|
|
|
|
const auto allEntryUrls = entry->getAllUrls();
|
|
|
|
for (const auto& entryUrl : allEntryUrls) {
|
|
|
|
if (handleURL(entryUrl, url, submitUrl, omitWwwSubdomain)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2020-05-20 14:53:42 +02:00
|
|
|
}
|
|
|
|
|
2023-10-25 17:12:55 +03:00
|
|
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
|
|
|
// Returns all Passkey entries for the current Relying Party
|
|
|
|
QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const StringPairList& keyList)
|
|
|
|
{
|
|
|
|
QList<Entry*> entries;
|
|
|
|
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
|
2023-11-23 06:11:25 +02:00
|
|
|
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
2023-10-25 17:12:55 +03:00
|
|
|
entries << entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all entries for the site that are allowed by the server
|
|
|
|
QList<Entry*> BrowserService::getPasskeyAllowedEntries(const QJsonObject& publicKey,
|
|
|
|
const QString& rpId,
|
|
|
|
const StringPairList& keyList)
|
|
|
|
{
|
|
|
|
QList<Entry*> entries;
|
|
|
|
const auto allowedCredentials = browserPasskeys()->getAllowedCredentialsFromPublicKey(publicKey);
|
|
|
|
|
|
|
|
for (const auto& entry : getPasskeyEntries(rpId, keyList)) {
|
|
|
|
// If allowedCredentials.isEmpty() check if entry contains an extra attribute for user handle.
|
|
|
|
// If that is found, the entry should be allowed.
|
|
|
|
// See: https://w3c.github.io/webauthn/#dom-authenticatorassertionresponse-userhandle
|
|
|
|
if (allowedCredentials.contains(entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID))
|
|
|
|
|| (allowedCredentials.isEmpty()
|
|
|
|
&& entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE))) {
|
|
|
|
entries << entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject
|
|
|
|
BrowserService::getPublicKeyCredentialFromEntry(const Entry* entry, const QJsonObject& publicKey, const QString& origin)
|
|
|
|
{
|
|
|
|
const auto privateKeyPem = entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM);
|
2023-11-08 07:19:13 +02:00
|
|
|
const auto credentialId = entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID);
|
2023-10-25 17:12:55 +03:00
|
|
|
const auto userHandle = entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE);
|
2023-11-08 07:19:13 +02:00
|
|
|
return browserPasskeys()->buildGetPublicKeyCredential(publicKey, origin, credentialId, userHandle, privateKeyPem);
|
2023-10-25 17:12:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if the same user ID already exists for the current site
|
|
|
|
bool BrowserService::isPasskeyCredentialExcluded(const QJsonArray& excludeCredentials,
|
|
|
|
const QString& origin,
|
|
|
|
const StringPairList& keyList)
|
|
|
|
{
|
|
|
|
QStringList allIds;
|
|
|
|
for (const auto& cred : excludeCredentials) {
|
|
|
|
allIds << cred["id"].toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto passkeyEntries = getPasskeyEntries(origin, keyList);
|
|
|
|
return std::any_of(passkeyEntries.begin(), passkeyEntries.end(), [&](const auto& entry) {
|
|
|
|
return allIds.contains(entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject BrowserService::getPasskeyError(int errorCode) const
|
|
|
|
{
|
|
|
|
return QJsonObject({{"errorCode", errorCode}});
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-06-05 10:37:00 +03:00
|
|
|
bool BrowserService::handleURL(const QString& entryUrl,
|
|
|
|
const QString& siteUrl,
|
|
|
|
const QString& formUrl,
|
|
|
|
const bool omitWwwSubdomain)
|
2019-08-15 12:35:11 +03:00
|
|
|
{
|
2019-11-01 20:13:12 +02:00
|
|
|
if (entryUrl.isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl entryQUrl;
|
|
|
|
if (entryUrl.contains("://")) {
|
|
|
|
entryQUrl = entryUrl;
|
|
|
|
} else {
|
|
|
|
entryQUrl = QUrl::fromUserInput(entryUrl);
|
|
|
|
|
|
|
|
if (browserSettings()->matchUrlScheme()) {
|
|
|
|
entryQUrl.setScheme("https");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 10:37:00 +03:00
|
|
|
// Remove WWW subdomain from matching if group setting is enabled
|
|
|
|
if (omitWwwSubdomain && entryQUrl.host().startsWith("www.")) {
|
|
|
|
entryQUrl.setHost(entryQUrl.host().remove("www."));
|
|
|
|
}
|
|
|
|
|
2019-11-12 22:38:20 +02:00
|
|
|
// Make a direct compare if a local file is used
|
2022-06-05 10:37:00 +03:00
|
|
|
if (siteUrl.startsWith("file://")) {
|
|
|
|
return entryUrl == formUrl;
|
2019-11-12 22:38:20 +02:00
|
|
|
}
|
|
|
|
|
2019-11-01 20:13:12 +02:00
|
|
|
// URL host validation fails
|
2019-11-12 22:38:20 +02:00
|
|
|
if (entryQUrl.host().isEmpty()) {
|
2019-11-01 20:13:12 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match port, if used
|
2022-06-05 10:37:00 +03:00
|
|
|
QUrl siteQUrl(siteUrl);
|
2019-11-12 22:38:20 +02:00
|
|
|
if (entryQUrl.port() > 0 && entryQUrl.port() != siteQUrl.port()) {
|
2019-11-01 20:13:12 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-08-15 12:35:11 +03:00
|
|
|
|
2019-11-01 20:13:12 +02:00
|
|
|
// Match scheme
|
2019-11-18 06:57:04 +00:00
|
|
|
if (browserSettings()->matchUrlScheme() && !entryQUrl.scheme().isEmpty()
|
|
|
|
&& entryQUrl.scheme().compare(siteQUrl.scheme()) != 0) {
|
2019-11-01 20:13:12 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for illegal characters
|
|
|
|
QRegularExpression re("[<>\\^`{|}]");
|
2019-11-12 22:38:20 +02:00
|
|
|
if (re.match(entryUrl).hasMatch()) {
|
2019-08-15 12:35:11 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-14 10:05:24 +02:00
|
|
|
// Match the base domain
|
2023-10-14 16:18:27 +03:00
|
|
|
if (urlTools()->getBaseDomainFromUrl(siteQUrl.host()) != urlTools()->getBaseDomainFromUrl(entryQUrl.host())) {
|
2020-01-14 10:05:24 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match the subdomains with the limited wildcard
|
|
|
|
if (siteQUrl.host().endsWith(entryQUrl.host())) {
|
2020-09-13 17:38:19 +03:00
|
|
|
return true;
|
2019-08-15 12:35:11 +03:00
|
|
|
}
|
2019-11-01 20:13:12 +02:00
|
|
|
|
2019-08-15 12:35:11 +03:00
|
|
|
return false;
|
2018-08-30 13:40:41 +03:00
|
|
|
}
|
|
|
|
|
2023-11-23 06:11:25 +02:00
|
|
|
QSharedPointer<Database> BrowserService::getDatabase(const QUuid& rootGroupUuid)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2023-11-23 06:11:25 +02:00
|
|
|
if (!rootGroupUuid.isNull()) {
|
|
|
|
const auto openDatabases = getOpenDatabases();
|
|
|
|
for (const auto& db : openDatabases) {
|
|
|
|
if (db->rootGroup()->uuid() == rootGroupUuid) {
|
|
|
|
return db;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
if (m_currentDatabaseWidget) {
|
|
|
|
return m_currentDatabaseWidget->database();
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2018-11-22 11:47:31 +01:00
|
|
|
return {};
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
|
2023-11-23 06:11:25 +02:00
|
|
|
QList<QSharedPointer<Database>> BrowserService::getOpenDatabases()
|
|
|
|
{
|
|
|
|
QList<QSharedPointer<Database>> databaseList;
|
|
|
|
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
|
|
|
if (!dbWidget->isLocked()) {
|
|
|
|
databaseList << dbWidget->database();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return databaseList;
|
|
|
|
}
|
|
|
|
|
2018-11-22 11:47:31 +01:00
|
|
|
QSharedPointer<Database> BrowserService::selectedDatabase()
|
2018-10-19 21:44:08 +03:00
|
|
|
{
|
|
|
|
QList<DatabaseWidget*> databaseWidgets;
|
2020-05-10 21:20:00 -04:00
|
|
|
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
2018-10-19 21:44:08 +03:00
|
|
|
// Add only open databases
|
2020-05-10 21:20:00 -04:00
|
|
|
if (!dbWidget->isLocked()) {
|
|
|
|
databaseWidgets << dbWidget;
|
2018-10-19 21:44:08 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-20 09:52:20 +03:00
|
|
|
BrowserEntrySaveDialog browserEntrySaveDialog(m_currentDatabaseWidget);
|
2020-05-10 21:20:00 -04:00
|
|
|
int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_currentDatabaseWidget);
|
2018-10-19 21:44:08 +03:00
|
|
|
if (openDatabaseCount > 1) {
|
|
|
|
int res = browserEntrySaveDialog.exec();
|
|
|
|
if (res == QDialog::Accepted) {
|
|
|
|
const auto selectedDatabase = browserEntrySaveDialog.getSelected();
|
|
|
|
if (selectedDatabase.length() > 0) {
|
2019-11-01 20:13:12 +02:00
|
|
|
int index = selectedDatabase[0]->data(Qt::UserRole).toInt();
|
2018-10-19 21:44:08 +03:00
|
|
|
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-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-11-01 20:13:12 +02:00
|
|
|
Q_UNUSED(force)
|
2019-03-19 08:30:49 -04:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-10-28 10:52:03 +02:00
|
|
|
void BrowserService::updateWindowState()
|
|
|
|
{
|
|
|
|
m_prevWindowState = WindowState::Normal;
|
|
|
|
if (getMainWindow()->isMinimized()) {
|
|
|
|
m_prevWindowState = WindowState::Minimized;
|
|
|
|
}
|
|
|
|
#ifdef Q_OS_MACOS
|
|
|
|
if (macUtils()->isHidden()) {
|
|
|
|
m_prevWindowState = WindowState::Hidden;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if (getMainWindow()->isHidden()) {
|
|
|
|
m_prevWindowState = WindowState::Hidden;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-12-12 10:15:23 +02:00
|
|
|
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
|
|
|
{
|
|
|
|
if (dbWidget) {
|
2020-05-10 21:20:00 -04:00
|
|
|
QJsonObject msg;
|
|
|
|
msg["action"] = QString("database-locked");
|
2022-02-23 00:52:51 +02:00
|
|
|
m_browserHost->broadcastClientMessage(msg);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
|
|
|
|
{
|
|
|
|
if (dbWidget) {
|
2020-07-05 14:32:23 +03:00
|
|
|
if (m_bringToFrontRequested) {
|
|
|
|
m_bringToFrontRequested = false;
|
|
|
|
hideWindow();
|
|
|
|
}
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
QJsonObject msg;
|
|
|
|
msg["action"] = QString("database-unlocked");
|
2022-02-23 00:52:51 +02:00
|
|
|
m_browserHost->broadcastClientMessage(msg);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 21:20:00 -04:00
|
|
|
void BrowserService::activeDatabaseChanged(DatabaseWidget* dbWidget)
|
2017-12-12 10:15:23 +02:00
|
|
|
{
|
2022-06-05 17:11:34 +03:00
|
|
|
if (dbWidget) {
|
2020-05-10 21:20:00 -04:00
|
|
|
if (dbWidget->isLocked()) {
|
|
|
|
databaseLocked(dbWidget);
|
2017-12-12 10:15:23 +02:00
|
|
|
} else {
|
2020-05-10 21:20:00 -04:00
|
|
|
databaseUnlocked(dbWidget);
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-24 18:38:33 +03:00
|
|
|
|
|
|
|
m_currentDatabaseWidget = dbWidget;
|
2017-12-12 10:15:23 +02:00
|
|
|
}
|
2020-05-10 21:20:00 -04:00
|
|
|
|
2022-02-23 00:52:51 +02:00
|
|
|
void BrowserService::processClientMessage(QLocalSocket* socket, const QJsonObject& message)
|
2020-05-10 21:20:00 -04:00
|
|
|
{
|
|
|
|
auto clientID = message["clientID"].toString();
|
|
|
|
if (clientID.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new client action if we haven't seen this id yet
|
|
|
|
if (!m_browserClients.contains(clientID)) {
|
|
|
|
m_browserClients.insert(clientID, QSharedPointer<BrowserAction>::create());
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& action = m_browserClients.value(clientID);
|
2022-02-23 00:52:51 +02:00
|
|
|
auto response = action->processClientMessage(socket, message);
|
|
|
|
m_browserHost->sendClientMessage(socket, response);
|
2020-05-10 21:20:00 -04:00
|
|
|
}
|