Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
run: |
sudo apt update
sudo apt install build-essential cmake g++
sudo apt install qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-base-dev-tools qt6-svg-dev qt6-5compat-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
sudo apt install qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-base-dev-tools qt6-svg-dev qt6-5compat-dev qt6-websockets-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
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
- .github/workflows/copilot-setup-steps.yml©

jobs:
copilot-setup-steps:
Expand All @@ -26,4 +26,4 @@ jobs:
- name: Install dependencies
run: |
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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ if(WITH_COVERAGE)
endif()

# Find Qt
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)
find_package(Qt6 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED)
elseif(APPLE)
Expand Down
16 changes: 11 additions & 5 deletions src/browser/BrowserAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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_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()) {
return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
Expand Down Expand Up @@ -73,10 +73,14 @@ QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJso
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
///////////////////////

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();

Expand Down Expand Up @@ -258,7 +262,8 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
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);
if (browserRequest.isEmpty()) {
Expand All @@ -277,11 +282,12 @@ QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJ
}

// Show the existing password generator
browserService()->showPasswordGenerator({});
// browserService()->showPasswordGenerator({});
browserService()->showPasswordGenerator(KeyPairMessage<T>{});
return errorReply;
}

KeyPairMessage keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};
KeyPairMessage<T> keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};

browserService()->showPasswordGenerator(keyPairMessage);
return {};
Expand Down
7 changes: 4 additions & 3 deletions src/browser/BrowserAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <QString>

class QLocalSocket;
class QWebSocket;

struct BrowserRequest
{
Expand Down Expand Up @@ -66,16 +67,16 @@ class BrowserAction
explicit BrowserAction() = default;
~BrowserAction() = default;

QJsonObject processClientMessage(QLocalSocket* socket, const QJsonObject& json);
template <typename T> QJsonObject processClientMessage(T* socket, const QJsonObject& json);

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 handleGetDatabaseHash(const QJsonObject& json, const QString& action);
QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
QJsonObject handleTestAssociate(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 handleLockDatabase(const QJsonObject& json, const QString& action);
QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action);
Expand Down
71 changes: 58 additions & 13 deletions src/browser/BrowserService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "BrowserPasskeysClient.h"
#include "BrowserPasskeysConfirmationDialog.h"
#include "BrowserSettings.h"
#include "BrowserWebSocketHost.h"
#include "PasskeyUtils.h"
#include "core/EntryAttributes.h"
#include "core/Tools.h"
Expand All @@ -51,6 +52,7 @@
#include <QProgressDialog>
#include <QStringView>
#include <QUrl>
#include <QtWebSockets/qwebsocket.h>

const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
Expand All @@ -74,12 +76,17 @@
BrowserService::BrowserService()
: QObject()
, m_browserHost(new BrowserHost)
, m_browserWebSocketHost(new BrowserWebSocketHost)
, m_dialogActive(false)
, m_bringToFrontRequested(false)
, m_prevWindowState(WindowState::Normal)
, 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::databaseLocked, this, &BrowserService::databaseLocked);
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
Expand All @@ -105,8 +112,12 @@
}

m_browserHost->start();
if (browserSettings()->webSocketSupport()) {
m_browserWebSocketHost->start();
}
} else {
m_browserHost->stop();
m_browserWebSocketHost->stop();
}
}

Expand Down Expand Up @@ -524,7 +535,7 @@
return allowedEntries;
}

void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
template <typename T> void BrowserService::showPasswordGenerator(const KeyPairMessage<T>& keyPairMessage)
{
if (!m_passwordGenerator) {
m_passwordGenerator = PasswordGeneratorWidget::popupGenerator();
Expand All @@ -536,7 +547,11 @@
if (!m_passwordGenerator->isPasswordGenerated()) {
auto errorMessage = browserMessageBuilder()->getErrorReply(
"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(); });
Expand All @@ -547,12 +562,24 @@
m_passwordGenerator.data(),
[this, keyPairMessage](const QString& password) {
const Parameters params{{"password", password}};
m_browserHost->sendClientMessage(keyPairMessage.socket,
browserMessageBuilder()->buildResponse("generate-password",
keyPairMessage.nonce,
params,
keyPairMessage.publicKey,
keyPairMessage.secretKey));
if constexpr (std::is_same<T, QWebSocket>::value) {
m_browserWebSocketHost->sendClientMessage(
keyPairMessage.socket,
browserMessageBuilder()->buildResponse("generate-password",
keyPairMessage.nonce,
params,
keyPairMessage.publicKey,
keyPairMessage.secretKey));

} else {
m_browserHost->sendClientMessage(
keyPairMessage.socket,
browserMessageBuilder()->buildResponse("generate-password",
keyPairMessage.nonce,
params,
keyPairMessage.publicKey,
keyPairMessage.secretKey));
}
});
}

Expand All @@ -562,6 +589,9 @@
m_passwordGenerator->activateWindow();
}

template void BrowserService::showPasswordGenerator<QLocalSocket>(const KeyPairMessage<QLocalSocket>&);
template void BrowserService::showPasswordGenerator<QWebSocket>(const KeyPairMessage<QWebSocket>&);

bool BrowserService::isPasswordGeneratorRequested() const
{
return m_passwordGenerator && m_passwordGenerator->isVisible();
Expand Down Expand Up @@ -1720,6 +1750,7 @@
QJsonObject msg;
msg["action"] = QString("database-locked");
m_browserHost->broadcastClientMessage(msg);
m_browserWebSocketHost->broadcastClientMessage(msg);
}
}

Expand All @@ -1734,6 +1765,7 @@
QJsonObject msg;
msg["action"] = QString("database-unlocked");
m_browserHost->broadcastClientMessage(msg);
m_browserWebSocketHost->broadcastClientMessage(msg);
}
}

Expand All @@ -1759,19 +1791,32 @@
}
}

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();
if (clientID.isEmpty()) {
return;
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());
}

const auto& action = m_browserClients.value(clientID);
auto response = action->processClientMessage(socket, message);
m_browserHost->sendClientMessage(socket, response);
return action->processClientMessage<T>(socket, message);
// auto response = action->processClientMessage(socket, message);
// m_browserHost->sendClientMessage(socket, response);
Comment on lines +1820 to +1821

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
}
15 changes: 10 additions & 5 deletions src/browser/BrowserService.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "gui/PasswordGeneratorWidget.h"

class QLocalSocket;
class QWebSocket;

typedef QPair<QString, QString> StringPair;
typedef QList<StringPair> StringPairList;
Expand All @@ -35,9 +36,9 @@ enum
max_length = 16 * 1024
};

struct KeyPairMessage
template <typename T> struct KeyPairMessage
{
QLocalSocket* socket;
T* socket;
QString nonce;
QString publicKey;
QString secretKey;
Expand All @@ -58,6 +59,7 @@ struct EntryParameters

class DatabaseWidget;
class BrowserHost;
class BrowserWebSocketHost;
class BrowserAction;

class BrowserService : public QObject
Expand All @@ -82,7 +84,7 @@ class BrowserService : public QObject
QJsonArray getDatabaseEntries();
QJsonObject createNewGroup(const QString& groupName, bool isPasskeysGroup = false);
QString getCurrentTotp(const QString& uuid);
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
template <typename T> void showPasswordGenerator(const KeyPairMessage<T>& keyPairMessage);
bool isPasswordGeneratorRequested() const;
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
QSharedPointer<Database> selectedDatabase();
Expand Down Expand Up @@ -137,15 +139,16 @@ class BrowserService : public QObject

signals:
void requestUnlock();
void passwordGenerated(QLocalSocket* socket, const QString& password, const QString& nonce);
void passwordGenerated(QWebSocket* socket, const QString& password, const QString& nonce);

public slots:
void databaseLocked(DatabaseWidget* dbWidget);
void databaseUnlocked(DatabaseWidget* dbWidget);
void activeDatabaseChanged(DatabaseWidget* dbWidget);

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);

private:
Expand Down Expand Up @@ -208,8 +211,10 @@ private slots:
void hideWindow() const;
void raiseWindow(const bool force = false);
void updateWindowState();
template <typename T> QJsonObject processClientMessage(T* socket, const QJsonObject& message);

QPointer<BrowserHost> m_browserHost;
QPointer<BrowserWebSocketHost> m_browserWebSocketHost;
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;

bool m_dialogActive;
Expand Down
11 changes: 10 additions & 1 deletion src/browser/BrowserSettings.cpp
Original file line number Diff line number Diff line change
@@ -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) 2013 Francois Ferrand
*
Expand Down Expand Up @@ -295,3 +295,12 @@ QString BrowserSettings::replaceTildeHomePath(QString location)

return location;
}

void BrowserSettings:: setWebSocketSupport(bool enabled)
{
config()->set(Config::Browser_WebSocketSupport, enabled);
}
bool BrowserSettings::webSocketSupport()
{
return config()->get(Config::Browser_WebSocketSupport).toBool();
}
4 changes: 3 additions & 1 deletion src/browser/BrowserSettings.h
Original file line number Diff line number Diff line change
@@ -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) 2013 Francois Ferrand
*
Expand Down Expand Up @@ -82,6 +82,8 @@ class BrowserSettings
void updateBinaryPaths();
QString replaceHomePath(QString location);
QString replaceTildeHomePath(QString location);
void setWebSocketSupport(bool enabled);
bool webSocketSupport();

private:
static BrowserSettings* m_instance;
Expand Down
4 changes: 3 additions & 1 deletion src/browser/BrowserSettingsWidget.cpp
Original file line number Diff line number Diff line change
@@ -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
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -166,6 +166,7 @@ void BrowserSettingsWidget::loadSettings()
m_ui->browserTypeComboBox->setCurrentIndex(typeIndex);
}
m_ui->customBrowserLocation->setText(settings->replaceHomePath(settings->customBrowserLocation()));
m_ui->webSocketSupport->setChecked(settings->webSocketSupport());

#ifdef QT_DEBUG
m_ui->customExtensionId->setText(settings->customExtensionId());
Expand Down Expand Up @@ -241,6 +242,7 @@ void BrowserSettingsWidget::saveSettings()
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
settings->setAllowLocalhostWithPasskeys(m_ui->allowLocalhostWithPasskeys->isChecked());
settings->setNoMigrationPrompt(m_ui->noMigrationPrompt->isChecked());
settings->setWebSocketSupport(m_ui->webSocketSupport->isChecked());

#ifdef QT_DEBUG
settings->setCustomExtensionId(m_ui->customExtensionId->text());
Expand Down
Loading
Loading