mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
Browser Integration: Add support for WebSocket listener
This commit is contained in:
parent
ada379fddd
commit
d8440490a3
17 changed files with 302 additions and 37 deletions
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install build-essential cmake g++
|
sudo apt install build-essential cmake g++
|
||||||
sudo apt install qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
|
sudo apt install qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev libqt5websockets5-dev
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|
|
||||||
4
.github/workflows/copilot-setup-steps.yml
vendored
4
.github/workflows/copilot-setup-steps.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
||||||
- .github/workflows/copilot-setup-steps.yml
|
- .github/workflows/copilot-setup-steps.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/copilot-setup-steps.yml
|
- .github/workflows/copilot-setup-steps.yml©
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
copilot-setup-steps:
|
copilot-setup-steps:
|
||||||
|
|
@ -26,4 +26,4 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install --no-install-recommends build-essential cmake g++ ninja-build qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
|
sudo apt install --no-install-recommends build-essential cmake g++ ninja-build qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev libqt5websockets5-dev
|
||||||
|
|
|
||||||
|
|
@ -510,7 +510,7 @@ endif()
|
||||||
|
|
||||||
include(CLangFormat)
|
include(CLangFormat)
|
||||||
|
|
||||||
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools)
|
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools WebSockets)
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
if(WITH_XC_X11)
|
if(WITH_XC_X11)
|
||||||
list(APPEND QT_COMPONENTS X11Extras)
|
list(APPEND QT_COMPONENTS X11Extras)
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ static const QString BROWSER_REQUEST_REQUEST_AUTOTYPE = QStringLiteral("request-
|
||||||
static const QString BROWSER_REQUEST_SET_LOGIN = QStringLiteral("set-login");
|
static const QString BROWSER_REQUEST_SET_LOGIN = QStringLiteral("set-login");
|
||||||
static const QString BROWSER_REQUEST_TEST_ASSOCIATE = QStringLiteral("test-associate");
|
static const QString BROWSER_REQUEST_TEST_ASSOCIATE = QStringLiteral("test-associate");
|
||||||
|
|
||||||
QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJsonObject& json)
|
template <typename T> QJsonObject BrowserAction::processClientMessage(T* socket, const QJsonObject& json)
|
||||||
{
|
{
|
||||||
if (json.isEmpty()) {
|
if (json.isEmpty()) {
|
||||||
return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
|
return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
|
||||||
|
|
@ -75,10 +75,14 @@ QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJso
|
||||||
return handleAction(socket, json);
|
return handleAction(socket, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicit template instantiation
|
||||||
|
template QJsonObject BrowserAction::processClientMessage<QLocalSocket>(QLocalSocket*, const QJsonObject&);
|
||||||
|
template QJsonObject BrowserAction::processClientMessage<QWebSocket>(QWebSocket*, const QJsonObject&);
|
||||||
|
|
||||||
// Private functions
|
// Private functions
|
||||||
///////////////////////
|
///////////////////////
|
||||||
|
|
||||||
QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject& json)
|
template <typename T> QJsonObject BrowserAction::handleAction(T* socket, const QJsonObject& json)
|
||||||
{
|
{
|
||||||
QString action = json.value("action").toString();
|
QString action = json.value("action").toString();
|
||||||
|
|
||||||
|
|
@ -262,7 +266,8 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
||||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action)
|
template <typename T>
|
||||||
|
QJsonObject BrowserAction::handleGeneratePassword(T* socket, const QJsonObject& json, const QString& action)
|
||||||
{
|
{
|
||||||
const auto browserRequest = decodeRequest(json);
|
const auto browserRequest = decodeRequest(json);
|
||||||
if (browserRequest.isEmpty()) {
|
if (browserRequest.isEmpty()) {
|
||||||
|
|
@ -281,11 +286,12 @@ QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJ
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the existing password generator
|
// Show the existing password generator
|
||||||
browserService()->showPasswordGenerator({});
|
// browserService()->showPasswordGenerator({});
|
||||||
|
browserService()->showPasswordGenerator(KeyPairMessage<T>{});
|
||||||
return errorReply;
|
return errorReply;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPairMessage keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};
|
KeyPairMessage<T> keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};
|
||||||
|
|
||||||
browserService()->showPasswordGenerator(keyPairMessage);
|
browserService()->showPasswordGenerator(keyPairMessage);
|
||||||
return {};
|
return {};
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
|
class QWebSocket;
|
||||||
|
|
||||||
struct BrowserRequest
|
struct BrowserRequest
|
||||||
{
|
{
|
||||||
|
|
@ -66,16 +67,16 @@ public:
|
||||||
explicit BrowserAction() = default;
|
explicit BrowserAction() = default;
|
||||||
~BrowserAction() = default;
|
~BrowserAction() = default;
|
||||||
|
|
||||||
QJsonObject processClientMessage(QLocalSocket* socket, const QJsonObject& json);
|
template <typename T> QJsonObject processClientMessage(T* socket, const QJsonObject& json);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QJsonObject handleAction(QLocalSocket* socket, const QJsonObject& json);
|
template <typename T> QJsonObject handleAction(T* socket, const QJsonObject& json);
|
||||||
QJsonObject handleChangePublicKeys(const QJsonObject& json, const QString& action);
|
QJsonObject handleChangePublicKeys(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
|
QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
|
QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action);
|
QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleGetLogins(const QJsonObject& json, const QString& action);
|
QJsonObject handleGetLogins(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action);
|
template <typename T> QJsonObject handleGeneratePassword(T* socket, const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
|
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
|
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action);
|
QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include "BrowserHost.h"
|
#include "BrowserHost.h"
|
||||||
#include "BrowserMessageBuilder.h"
|
#include "BrowserMessageBuilder.h"
|
||||||
#include "BrowserSettings.h"
|
#include "BrowserSettings.h"
|
||||||
|
#include "BrowserWebSocketHost.h"
|
||||||
#include "core/EntryAttributes.h"
|
#include "core/EntryAttributes.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
#include "gui/MainWindow.h"
|
#include "gui/MainWindow.h"
|
||||||
|
|
@ -53,6 +54,7 @@
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
#include <QStringView>
|
#include <QStringView>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QtWebSockets/qwebsocket.h>
|
||||||
|
|
||||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||||
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
||||||
|
|
@ -78,12 +80,17 @@ Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
||||||
BrowserService::BrowserService()
|
BrowserService::BrowserService()
|
||||||
: QObject()
|
: QObject()
|
||||||
, m_browserHost(new BrowserHost)
|
, m_browserHost(new BrowserHost)
|
||||||
|
, m_browserWebSocketHost(new BrowserWebSocketHost)
|
||||||
, m_dialogActive(false)
|
, m_dialogActive(false)
|
||||||
, m_bringToFrontRequested(false)
|
, m_bringToFrontRequested(false)
|
||||||
, m_prevWindowState(WindowState::Normal)
|
, m_prevWindowState(WindowState::Normal)
|
||||||
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
||||||
{
|
{
|
||||||
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
|
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processLocalSocketClientMessage);
|
||||||
|
connect(m_browserWebSocketHost,
|
||||||
|
&BrowserWebSocketHost::clientMessageReceived,
|
||||||
|
this,
|
||||||
|
&BrowserService::processWebSocketClientMessage);
|
||||||
connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked);
|
connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked);
|
||||||
connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked);
|
connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked);
|
||||||
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
|
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
|
||||||
|
|
@ -109,8 +116,12 @@ void BrowserService::setEnabled(bool enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
m_browserHost->start();
|
m_browserHost->start();
|
||||||
|
if (browserSettings()->webSocketSupport()) {
|
||||||
|
m_browserWebSocketHost->start();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
m_browserHost->stop();
|
m_browserHost->stop();
|
||||||
|
m_browserWebSocketHost->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -528,7 +539,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& entriesToConfirm,
|
||||||
return allowedEntries;
|
return allowedEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
template <typename T> void BrowserService::showPasswordGenerator(const KeyPairMessage<T>& keyPairMessage)
|
||||||
{
|
{
|
||||||
if (!m_passwordGenerator) {
|
if (!m_passwordGenerator) {
|
||||||
m_passwordGenerator = PasswordGeneratorWidget::popupGenerator();
|
m_passwordGenerator = PasswordGeneratorWidget::popupGenerator();
|
||||||
|
|
@ -540,7 +551,11 @@ void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
||||||
if (!m_passwordGenerator->isPasswordGenerated()) {
|
if (!m_passwordGenerator->isPasswordGenerated()) {
|
||||||
auto errorMessage = browserMessageBuilder()->getErrorReply(
|
auto errorMessage = browserMessageBuilder()->getErrorReply(
|
||||||
"generate-password", ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
"generate-password", ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||||
m_browserHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
if constexpr (std::is_same<T, QWebSocket>::value) {
|
||||||
|
m_browserWebSocketHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
||||||
|
} else {
|
||||||
|
m_browserHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QTimer::singleShot(50, this, [&] { hideWindow(); });
|
QTimer::singleShot(50, this, [&] { hideWindow(); });
|
||||||
|
|
@ -551,12 +566,24 @@ void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
||||||
m_passwordGenerator.data(),
|
m_passwordGenerator.data(),
|
||||||
[this, keyPairMessage](const QString& password) {
|
[this, keyPairMessage](const QString& password) {
|
||||||
const Parameters params{{"password", password}};
|
const Parameters params{{"password", password}};
|
||||||
m_browserHost->sendClientMessage(keyPairMessage.socket,
|
if constexpr (std::is_same<T, QWebSocket>::value) {
|
||||||
browserMessageBuilder()->buildResponse("generate-password",
|
m_browserWebSocketHost->sendClientMessage(
|
||||||
keyPairMessage.nonce,
|
keyPairMessage.socket,
|
||||||
params,
|
browserMessageBuilder()->buildResponse("generate-password",
|
||||||
keyPairMessage.publicKey,
|
keyPairMessage.nonce,
|
||||||
keyPairMessage.secretKey));
|
params,
|
||||||
|
keyPairMessage.publicKey,
|
||||||
|
keyPairMessage.secretKey));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
m_browserHost->sendClientMessage(
|
||||||
|
keyPairMessage.socket,
|
||||||
|
browserMessageBuilder()->buildResponse("generate-password",
|
||||||
|
keyPairMessage.nonce,
|
||||||
|
params,
|
||||||
|
keyPairMessage.publicKey,
|
||||||
|
keyPairMessage.secretKey));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -566,6 +593,9 @@ void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
||||||
m_passwordGenerator->activateWindow();
|
m_passwordGenerator->activateWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template void BrowserService::showPasswordGenerator<QLocalSocket>(const KeyPairMessage<QLocalSocket>&);
|
||||||
|
template void BrowserService::showPasswordGenerator<QWebSocket>(const KeyPairMessage<QWebSocket>&);
|
||||||
|
|
||||||
bool BrowserService::isPasswordGeneratorRequested() const
|
bool BrowserService::isPasswordGeneratorRequested() const
|
||||||
{
|
{
|
||||||
return m_passwordGenerator && m_passwordGenerator->isVisible();
|
return m_passwordGenerator && m_passwordGenerator->isVisible();
|
||||||
|
|
@ -1715,6 +1745,7 @@ void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
||||||
QJsonObject msg;
|
QJsonObject msg;
|
||||||
msg["action"] = QString("database-locked");
|
msg["action"] = QString("database-locked");
|
||||||
m_browserHost->broadcastClientMessage(msg);
|
m_browserHost->broadcastClientMessage(msg);
|
||||||
|
m_browserWebSocketHost->broadcastClientMessage(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1729,6 +1760,7 @@ void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
|
||||||
QJsonObject msg;
|
QJsonObject msg;
|
||||||
msg["action"] = QString("database-unlocked");
|
msg["action"] = QString("database-unlocked");
|
||||||
m_browserHost->broadcastClientMessage(msg);
|
m_browserHost->broadcastClientMessage(msg);
|
||||||
|
m_browserWebSocketHost->broadcastClientMessage(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1754,11 +1786,23 @@ void BrowserService::handleDatabaseUnlockDialogFinished(bool accepted, DatabaseW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BrowserService::processClientMessage(QLocalSocket* socket, const QJsonObject& message)
|
void BrowserService::processLocalSocketClientMessage(QLocalSocket* socket, const QJsonObject& message)
|
||||||
|
{
|
||||||
|
auto response = processClientMessage<QLocalSocket>(socket, message);
|
||||||
|
m_browserHost->sendClientMessage(socket, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserService::processWebSocketClientMessage(QWebSocket* socket, const QJsonObject& message)
|
||||||
|
{
|
||||||
|
auto response = processClientMessage<QWebSocket>(socket, message);
|
||||||
|
m_browserWebSocketHost->sendClientMessage(socket, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> QJsonObject BrowserService::processClientMessage(T* socket, const QJsonObject& message)
|
||||||
{
|
{
|
||||||
auto clientID = message["clientID"].toString();
|
auto clientID = message["clientID"].toString();
|
||||||
if (clientID.isEmpty()) {
|
if (clientID.isEmpty()) {
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new client action if we haven't seen this id yet
|
// Create a new client action if we haven't seen this id yet
|
||||||
|
|
@ -1767,6 +1811,7 @@ void BrowserService::processClientMessage(QLocalSocket* socket, const QJsonObjec
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& action = m_browserClients.value(clientID);
|
auto& action = m_browserClients.value(clientID);
|
||||||
auto response = action->processClientMessage(socket, message);
|
return action->processClientMessage<T>(socket, message);
|
||||||
m_browserHost->sendClientMessage(socket, response);
|
// auto response = action->processClientMessage(socket, message);
|
||||||
|
// m_browserHost->sendClientMessage(socket, response);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include "gui/PasswordGeneratorWidget.h"
|
#include "gui/PasswordGeneratorWidget.h"
|
||||||
|
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
|
class QWebSocket;
|
||||||
|
|
||||||
typedef QPair<QString, QString> StringPair;
|
typedef QPair<QString, QString> StringPair;
|
||||||
typedef QList<StringPair> StringPairList;
|
typedef QList<StringPair> StringPairList;
|
||||||
|
|
@ -35,9 +36,9 @@ enum
|
||||||
max_length = 16 * 1024
|
max_length = 16 * 1024
|
||||||
};
|
};
|
||||||
|
|
||||||
struct KeyPairMessage
|
template <typename T> struct KeyPairMessage
|
||||||
{
|
{
|
||||||
QLocalSocket* socket;
|
T* socket;
|
||||||
QString nonce;
|
QString nonce;
|
||||||
QString publicKey;
|
QString publicKey;
|
||||||
QString secretKey;
|
QString secretKey;
|
||||||
|
|
@ -58,6 +59,7 @@ struct EntryParameters
|
||||||
|
|
||||||
class DatabaseWidget;
|
class DatabaseWidget;
|
||||||
class BrowserHost;
|
class BrowserHost;
|
||||||
|
class BrowserWebSocketHost;
|
||||||
class BrowserAction;
|
class BrowserAction;
|
||||||
|
|
||||||
class BrowserService : public QObject
|
class BrowserService : public QObject
|
||||||
|
|
@ -82,7 +84,7 @@ public:
|
||||||
QJsonArray getDatabaseEntries();
|
QJsonArray getDatabaseEntries();
|
||||||
QJsonObject createNewGroup(const QString& groupName, bool isPasskeysGroup = false);
|
QJsonObject createNewGroup(const QString& groupName, bool isPasskeysGroup = false);
|
||||||
QString getCurrentTotp(const QString& uuid);
|
QString getCurrentTotp(const QString& uuid);
|
||||||
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
template <typename T> void showPasswordGenerator(const KeyPairMessage<T>& keyPairMessage);
|
||||||
bool isPasswordGeneratorRequested() const;
|
bool isPasswordGeneratorRequested() const;
|
||||||
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
|
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
|
||||||
QSharedPointer<Database> selectedDatabase();
|
QSharedPointer<Database> selectedDatabase();
|
||||||
|
|
@ -137,7 +139,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void requestUnlock();
|
void requestUnlock();
|
||||||
void passwordGenerated(QLocalSocket* socket, const QString& password, const QString& nonce);
|
void passwordGenerated(QWebSocket* socket, const QString& password, const QString& nonce);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void databaseLocked(DatabaseWidget* dbWidget);
|
void databaseLocked(DatabaseWidget* dbWidget);
|
||||||
|
|
@ -145,7 +147,8 @@ public slots:
|
||||||
void activeDatabaseChanged(DatabaseWidget* dbWidget);
|
void activeDatabaseChanged(DatabaseWidget* dbWidget);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void processClientMessage(QLocalSocket* socket, const QJsonObject& message);
|
void processLocalSocketClientMessage(QLocalSocket* socket, const QJsonObject& message);
|
||||||
|
void processWebSocketClientMessage(QWebSocket* socket, const QJsonObject& message);
|
||||||
void handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
void handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -208,8 +211,10 @@ private:
|
||||||
void hideWindow() const;
|
void hideWindow() const;
|
||||||
void raiseWindow(const bool force = false);
|
void raiseWindow(const bool force = false);
|
||||||
void updateWindowState();
|
void updateWindowState();
|
||||||
|
template <typename T> QJsonObject processClientMessage(T* socket, const QJsonObject& message);
|
||||||
|
|
||||||
QPointer<BrowserHost> m_browserHost;
|
QPointer<BrowserHost> m_browserHost;
|
||||||
|
QPointer<BrowserWebSocketHost> m_browserWebSocketHost;
|
||||||
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;
|
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;
|
||||||
|
|
||||||
bool m_dialogActive;
|
bool m_dialogActive;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||||
* Copyright (C) 2013 Francois Ferrand
|
* Copyright (C) 2013 Francois Ferrand
|
||||||
*
|
*
|
||||||
|
|
@ -295,3 +295,12 @@ QString BrowserSettings::replaceTildeHomePath(QString location)
|
||||||
|
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BrowserSettings:: setWebSocketSupport(bool enabled)
|
||||||
|
{
|
||||||
|
config()->set(Config::Browser_WebSocketSupport, enabled);
|
||||||
|
}
|
||||||
|
bool BrowserSettings::webSocketSupport()
|
||||||
|
{
|
||||||
|
return config()->get(Config::Browser_WebSocketSupport).toBool();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||||
* Copyright (C) 2013 Francois Ferrand
|
* Copyright (C) 2013 Francois Ferrand
|
||||||
*
|
*
|
||||||
|
|
@ -82,6 +82,8 @@ public:
|
||||||
void updateBinaryPaths();
|
void updateBinaryPaths();
|
||||||
QString replaceHomePath(QString location);
|
QString replaceHomePath(QString location);
|
||||||
QString replaceTildeHomePath(QString location);
|
QString replaceTildeHomePath(QString location);
|
||||||
|
void setWebSocketSupport(bool enabled);
|
||||||
|
bool webSocketSupport();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static BrowserSettings* m_instance;
|
static BrowserSettings* m_instance;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -166,6 +166,7 @@ void BrowserSettingsWidget::loadSettings()
|
||||||
m_ui->browserTypeComboBox->setCurrentIndex(typeIndex);
|
m_ui->browserTypeComboBox->setCurrentIndex(typeIndex);
|
||||||
}
|
}
|
||||||
m_ui->customBrowserLocation->setText(settings->replaceHomePath(settings->customBrowserLocation()));
|
m_ui->customBrowserLocation->setText(settings->replaceHomePath(settings->customBrowserLocation()));
|
||||||
|
m_ui->webSocketSupport->setChecked(settings->webSocketSupport());
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
m_ui->customExtensionId->setText(settings->customExtensionId());
|
m_ui->customExtensionId->setText(settings->customExtensionId());
|
||||||
|
|
@ -241,6 +242,7 @@ void BrowserSettingsWidget::saveSettings()
|
||||||
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
|
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
|
||||||
settings->setAllowLocalhostWithPasskeys(m_ui->allowLocalhostWithPasskeys->isChecked());
|
settings->setAllowLocalhostWithPasskeys(m_ui->allowLocalhostWithPasskeys->isChecked());
|
||||||
settings->setNoMigrationPrompt(m_ui->noMigrationPrompt->isChecked());
|
settings->setNoMigrationPrompt(m_ui->noMigrationPrompt->isChecked());
|
||||||
|
settings->setWebSocketSupport(m_ui->webSocketSupport->isChecked());
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
settings->setCustomExtensionId(m_ui->customExtensionId->text());
|
settings->setCustomExtensionId(m_ui->customExtensionId->text());
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="webSocketSupport">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Listens to connections using WebSocket in addition to native messaging.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable WebSocket listener</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="allowGetDatabaseEntriesRequest">
|
<widget class="QCheckBox" name="allowGetDatabaseEntriesRequest">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
|
|
|
||||||
123
src/browser/BrowserWebSocketHost.cpp
Normal file
123
src/browser/BrowserWebSocketHost.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BrowserShared.h"
|
||||||
|
#include "BrowserWebSocketHost.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QtWebSockets/qwebsocket.h>
|
||||||
|
#include <QtWebSockets/qwebsocketserver.h>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <fcntl.h>
|
||||||
|
#undef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BrowserWebSocketHost::BrowserWebSocketHost(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
m_webSocketServer =
|
||||||
|
new QWebSocketServer(QStringLiteral("KeePassXC HTTP server"), QWebSocketServer::NonSecureMode, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserWebSocketHost::~BrowserWebSocketHost()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::start()
|
||||||
|
{
|
||||||
|
int socketDesc = m_webSocketServer->nativeDescriptor();
|
||||||
|
if (socketDesc) {
|
||||||
|
int max = BrowserShared::NATIVEMSG_MAX_LENGTH;
|
||||||
|
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_webSocketServer->isListening()) {
|
||||||
|
m_webSocketServer->listen(QHostAddress::LocalHost, 7580);
|
||||||
|
connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &BrowserWebSocketHost::clientConnected);
|
||||||
|
connect(m_webSocketServer, &QWebSocketServer::closed, this, &BrowserWebSocketHost::stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::stop()
|
||||||
|
{
|
||||||
|
m_socketList.clear();
|
||||||
|
m_webSocketServer->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::clientConnected()
|
||||||
|
{
|
||||||
|
auto socket = m_webSocketServer->nextPendingConnection();
|
||||||
|
if (socket) {
|
||||||
|
m_socketList.append(socket);
|
||||||
|
connect(socket, &QWebSocket::textMessageReceived, this, &BrowserWebSocketHost::readClientMessage);
|
||||||
|
connect(socket, &QWebSocket::disconnected, this, &BrowserWebSocketHost::clientDisconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::readClientMessage(QString message)
|
||||||
|
{
|
||||||
|
auto* socket = qobject_cast<QWebSocket*>(QObject::sender());
|
||||||
|
if (!socket || !socket->isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||||
|
socket->setOutgoingFrameSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
auto json = QJsonDocument::fromJson(message.toUtf8(), &error);
|
||||||
|
if (json.isNull()) {
|
||||||
|
qWarning() << "Failed to read proxy message: " << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit clientMessageReceived(socket, json.object());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::broadcastClientMessage(const QJsonObject& json)
|
||||||
|
{
|
||||||
|
QString reply(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||||
|
for (const auto socket : m_socketList) {
|
||||||
|
sendClientData(socket, reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::sendClientMessage(QWebSocket* socket, const QJsonObject& json)
|
||||||
|
{
|
||||||
|
QString reply(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||||
|
sendClientData(socket, reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::sendClientData(QWebSocket* socket, const QString& data)
|
||||||
|
{
|
||||||
|
if (socket && socket->isValid() && socket->state() == QAbstractSocket::ConnectedState) {
|
||||||
|
socket->sendTextMessage(data);
|
||||||
|
socket->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserWebSocketHost::clientDisconnected()
|
||||||
|
{
|
||||||
|
auto socket = qobject_cast<QWebSocket*>(QObject::sender());
|
||||||
|
m_socketList.removeOne(socket);
|
||||||
|
}
|
||||||
59
src/browser/BrowserWebSocketHost.h
Normal file
59
src/browser/BrowserWebSocketHost.h
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSXC_BROWSERWEBSOCKETHOST_H
|
||||||
|
#define KEEPASSXC_BROWSERWEBSOCKETHOST_H
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
class QWebSocketServer;
|
||||||
|
class QWebSocket;
|
||||||
|
class QString;
|
||||||
|
|
||||||
|
class BrowserWebSocketHost : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BrowserWebSocketHost(QObject* parent = nullptr);
|
||||||
|
~BrowserWebSocketHost() override;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void broadcastClientMessage(const QJsonObject& json);
|
||||||
|
void sendClientMessage(QWebSocket* socket, const QJsonObject& json);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void clientMessageReceived(QWebSocket* socket, const QJsonObject& json);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void clientConnected();
|
||||||
|
void readClientMessage(QString message);
|
||||||
|
void clientDisconnected();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sendClientData(QWebSocket* socket, const QString& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<QWebSocketServer> m_webSocketServer;
|
||||||
|
QList<QWebSocket*> m_socketList;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_BROWSERWEBSOCKETHOST_H
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
# Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -28,6 +28,7 @@ if(WITH_XC_BROWSER)
|
||||||
BrowserService.cpp
|
BrowserService.cpp
|
||||||
BrowserSettings.cpp
|
BrowserSettings.cpp
|
||||||
BrowserShared.cpp
|
BrowserShared.cpp
|
||||||
|
BrowserWebSocketHost.cpp
|
||||||
CustomTableWidget.cpp
|
CustomTableWidget.cpp
|
||||||
NativeMessageInstaller.cpp)
|
NativeMessageInstaller.cpp)
|
||||||
|
|
||||||
|
|
@ -41,5 +42,5 @@ if(WITH_XC_BROWSER)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(browser STATIC ${browser_SOURCES})
|
add_library(browser STATIC ${browser_SOURCES})
|
||||||
target_link_libraries(browser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${BOTAN_LIBRARIES})
|
target_link_libraries(browser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network Qt5::WebSockets ${BOTAN_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||||
{Config::Browser_CustomBrowserType, {QS("Browser/CustomBrowserType"), Local, -1}},
|
{Config::Browser_CustomBrowserType, {QS("Browser/CustomBrowserType"), Local, -1}},
|
||||||
{Config::Browser_CustomBrowserLocation, {QS("Browser/CustomBrowserLocation"), Local, {}}},
|
{Config::Browser_CustomBrowserLocation, {QS("Browser/CustomBrowserLocation"), Local, {}}},
|
||||||
{Config::Browser_AllowLocalhostWithPasskeys, {QS("Browser/Browser_AllowLocalhostWithPasskeys"), Roaming, false}},
|
{Config::Browser_AllowLocalhostWithPasskeys, {QS("Browser/Browser_AllowLocalhostWithPasskeys"), Roaming, false}},
|
||||||
|
{Config::Browser_WebSocketSupport, {QS("Browser/WebSocketSupport"), Roaming, false}},
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
{Config::Browser_CustomExtensionId, {QS("Browser/CustomExtensionId"), Local, {}}},
|
{Config::Browser_CustomExtensionId, {QS("Browser/CustomExtensionId"), Local, {}}},
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -159,6 +159,7 @@ public:
|
||||||
Browser_CustomBrowserType,
|
Browser_CustomBrowserType,
|
||||||
Browser_CustomBrowserLocation,
|
Browser_CustomBrowserLocation,
|
||||||
Browser_AllowLocalhostWithPasskeys,
|
Browser_AllowLocalhostWithPasskeys,
|
||||||
|
Browser_WebSocketSupport,
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
Browser_CustomExtensionId,
|
Browser_CustomExtensionId,
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ void TestBrowser::testChangePublicKeys()
|
||||||
json["publicKey"] = PUBLICKEY;
|
json["publicKey"] = PUBLICKEY;
|
||||||
json["nonce"] = NONCE;
|
json["nonce"] = NONCE;
|
||||||
|
|
||||||
auto response = m_browserAction->processClientMessage(nullptr, json);
|
auto response = m_browserAction->processClientMessage<QLocalSocket>(nullptr, json);
|
||||||
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
||||||
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
||||||
QCOMPARE(response["success"].toString(), TRUE_STR);
|
QCOMPARE(response["success"].toString(), TRUE_STR);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue