Complete refactor of Browser Integration classes

* Removed option to attach KeePassXC to the browser extension. Users must use the proxy application to communicate with KeePassXC.
* Significantly streamlined proxy code. Used same implementation of stdin/stdout interface across all platforms.
* Moved browser service entry point to BrowserService class instead of NativeMessagingHost. BrowserService now coordinates the communication to/from clients.
* Moved settings page definition out of MainWindow
* Decoupled BrowserService from DatabaseTabWidget
* Reduced complexity of various functions and cleaned the ABI (public vs private).
* Eliminated BrowserClients class, moved functionality into the BrowserService
* Renamed HostInstaller to NativeMessageInstaller and renamed NativeMessageHost to BrowserHost.
* Recognize XDG_CONFIG_HOME when installing native message file on Linux. Fix #4121 and fix #4123.
This commit is contained in:
Jonathan White 2020-05-10 21:20:00 -04:00
parent 3b4057a78c
commit a145bf9119
43 changed files with 1221 additions and 1919 deletions

View File

@ -233,7 +233,6 @@ if(APPLE)
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
endif()
set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser)
add_subdirectory(browser)
add_subdirectory(proxy)
if(WITH_XC_BROWSER)

View File

@ -1,6 +1,5 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -17,9 +16,11 @@
*/
#include "BrowserAction.h"
#include "BrowserService.h"
#include "BrowserSettings.h"
#include "NativeMessagingBase.h"
#include "BrowserShared.h"
#include "config-keepassx.h"
#include "core/Global.h"
#include <QJsonDocument>
#include <QJsonParseError>
@ -27,14 +28,31 @@
#include <sodium/crypto_box.h>
#include <sodium/randombytes.h>
BrowserAction::BrowserAction(BrowserService& browserService)
: m_mutex(QMutex::Recursive)
, m_browserService(browserService)
, m_associated(false)
namespace
{
enum
{
ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
ERROR_KEEPASS_INCORRECT_ACTION = 12,
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
ERROR_KEEPASS_NO_URL_PROVIDED = 14,
ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17
};
}
QJsonObject BrowserAction::readResponse(const QJsonObject& json)
QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
{
if (json.isEmpty()) {
return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
@ -51,11 +69,10 @@ QJsonObject BrowserAction::readResponse(const QJsonObject& json)
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
}
QMutexLocker locker(&m_mutex);
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !browserService()->isDatabaseOpened()) {
if (m_clientPublicKey.isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
} else if (!m_browserService.openDatabase(triggerUnlock)) {
} else if (!browserService()->openDatabase(triggerUnlock)) {
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
}
}
@ -98,7 +115,6 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json)
QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
{
QMutexLocker locker(&m_mutex);
const QString nonce = json.value("nonce").toString();
const QString clientPublicKey = json.value("publicKey").toString();
@ -130,7 +146,7 @@ QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const
QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
@ -153,7 +169,7 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const
// Update a legacy database hash if found
const QJsonArray hashes = decrypted.value("connectedKeys").toArray();
if (!hashes.isEmpty()) {
const QString legacyHash = getLegacyDatabaseHash();
const QString legacyHash = browserService()->getDatabaseHash(true);
if (hashes.contains(legacyHash)) {
message["oldHash"] = legacyHash;
}
@ -167,7 +183,7 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const
QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
@ -181,12 +197,11 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
QMutexLocker locker(&m_mutex);
if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
// Check for identification key. If it's not found, ensure backwards compatibility and use the current public
// key
const QString idKey = decrypted.value("idKey").toString();
const QString id = m_browserService.storeKey((idKey.isEmpty() ? key : idKey));
const QString id = browserService()->storeKey((idKey.isEmpty() ? key : idKey));
if (id.isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
}
@ -205,7 +220,7 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
@ -220,8 +235,7 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
}
QMutexLocker locker(&m_mutex);
const QString key = m_browserService.getKey(id);
const QString key = browserService()->getKey(id);
if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
@ -238,11 +252,10 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS
QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
QMutexLocker locker(&m_mutex);
if (!m_associated) {
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
@ -269,7 +282,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
const QString submit = decrypted.value("submitUrl").toString();
const QString auth = decrypted.value("httpAuth").toString();
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList, httpAuth);
const QJsonArray users = browserService()->findMatchingEntries(id, url, submit, "", keyList, httpAuth);
if (users.isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
@ -311,11 +324,10 @@ QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const
QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
QMutexLocker locker(&m_mutex);
if (!m_associated) {
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
@ -339,11 +351,11 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
const QString groupUuid = decrypted.value("groupUuid").toString();
const QString realm;
BrowserService::ReturnValue result = BrowserService::ReturnValue::Success;
bool result = true;
if (uuid.isEmpty()) {
m_browserService.addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
browserService()->addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
} else {
result = m_browserService.updateEntry(id, uuid, login, password, url, submitUrl);
result = browserService()->updateEntry(id, uuid, login, password, url, submitUrl);
}
const QString newNonce = incrementNonce(nonce);
@ -351,7 +363,7 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
QJsonObject message = buildMessage(newNonce);
message["count"] = QJsonValue::Null;
message["entries"] = QJsonValue::Null;
message["error"] = getReturnValue(result);
message["error"] = result ? QStringLiteral("success") : QStringLiteral("error");
message["hash"] = hash;
return buildResponse(action, message, newNonce);
@ -359,7 +371,7 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
@ -374,8 +386,7 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
QString command = decrypted.value("action").toString();
if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
QMutexLocker locker(&m_mutex);
m_browserService.lockDatabase();
browserService()->lockDatabase();
const QString newNonce = incrementNonce(nonce);
QJsonObject message = buildMessage(newNonce);
@ -388,11 +399,10 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
QMutexLocker locker(&m_mutex);
if (!m_associated) {
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
@ -407,7 +417,7 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
}
const QJsonObject groups = m_browserService.getDatabaseGroups();
const QJsonObject groups = browserService()->getDatabaseGroups();
if (groups.isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_NO_GROUPS_FOUND);
}
@ -422,11 +432,10 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons
QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action)
{
const QString hash = getDatabaseHash();
const QString hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
QMutexLocker locker(&m_mutex);
if (!m_associated) {
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
@ -442,7 +451,7 @@ QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const Q
}
QString group = decrypted.value("groupName").toString();
const QJsonObject newGroup = m_browserService.createNewGroup(group);
const QJsonObject newGroup = browserService()->createNewGroup(group);
if (newGroup.isEmpty() || newGroup["name"].toString().isEmpty() || newGroup["uuid"].toString().isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP);
}
@ -524,38 +533,6 @@ QString BrowserAction::getErrorMessage(const int errorCode) const
}
}
QString BrowserAction::getReturnValue(const BrowserService::ReturnValue returnValue) const
{
switch (returnValue) {
case BrowserService::ReturnValue::Success:
return QString("success");
case BrowserService::ReturnValue::Error:
return QString("error");
case BrowserService::ReturnValue::Canceled:
return QString("canceled");
}
return QString("error");
}
QString BrowserAction::getDatabaseHash()
{
QMutexLocker locker(&m_mutex);
QByteArray hash =
QCryptographicHash::hash(m_browserService.getDatabaseRootUuid().toUtf8(), QCryptographicHash::Sha256).toHex();
return QString(hash);
}
QString BrowserAction::getLegacyDatabaseHash()
{
QMutexLocker locker(&m_mutex);
QByteArray hash =
QCryptographicHash::hash(
(m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
QCryptographicHash::Sha256)
.toHex();
return QString(hash);
}
QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
{
if (message.isEmpty() || nonce.isEmpty()) {
@ -586,7 +563,6 @@ QJsonObject BrowserAction::decryptMessage(const QString& message, const QString&
QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
{
QMutexLocker locker(&m_mutex);
const QByteArray ma = plaintext.toUtf8();
const QByteArray na = base64Decode(nonce);
const QByteArray ca = base64Decode(m_clientPublicKey);
@ -598,7 +574,7 @@ QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
std::vector<unsigned char> e;
e.resize(NATIVE_MSG_MAX_LENGTH);
e.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
return QString();
@ -614,7 +590,6 @@ QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
QByteArray BrowserAction::decrypt(const QString& encrypted, const QString& nonce)
{
QMutexLocker locker(&m_mutex);
const QByteArray ma = base64Decode(encrypted);
const QByteArray na = base64Decode(nonce);
const QByteArray ca = base64Decode(m_clientPublicKey);
@ -626,7 +601,7 @@ QByteArray BrowserAction::decrypt(const QString& encrypted, const QString& nonce
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
std::vector<unsigned char> d;
d.resize(NATIVE_MSG_MAX_LENGTH);
d.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
return QByteArray();

View File

@ -1,6 +1,5 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -19,42 +18,16 @@
#ifndef BROWSERACTION_H
#define BROWSERACTION_H
#include "BrowserService.h"
#include <QJsonObject>
#include <QMutex>
#include <QObject>
#include <QtCore>
#include <QString>
class BrowserAction : public QObject
class BrowserAction
{
Q_OBJECT
enum
{
ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
ERROR_KEEPASS_INCORRECT_ACTION = 12,
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
ERROR_KEEPASS_NO_URL_PROVIDED = 14,
ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17
};
public:
BrowserAction(BrowserService& browserService);
explicit BrowserAction() = default;
~BrowserAction() = default;
QJsonObject readResponse(const QJsonObject& json);
QJsonObject processClientMessage(const QJsonObject& json);
private:
QJsonObject handleAction(const QJsonObject& json);
@ -73,9 +46,6 @@ private:
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
QString getErrorMessage(const int errorCode) const;
QString getReturnValue(const BrowserService::ReturnValue returnValue) const;
QString getDatabaseHash();
QString getLegacyDatabaseHash();
QString encryptMessage(const QJsonObject& message, const QString& nonce);
QJsonObject decryptMessage(const QString& message, const QString& nonce);
@ -90,12 +60,10 @@ private:
QString incrementNonce(const QString& nonce);
private:
QMutex m_mutex;
BrowserService& m_browserService;
QString m_clientPublicKey;
QString m_publicKey;
QString m_secretKey;
bool m_associated;
bool m_associated = false;
friend class TestBrowser;
};

View File

@ -1,77 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BrowserClients.h"
#include <QJsonParseError>
#include <QJsonValue>
BrowserClients::BrowserClients(BrowserService& browserService)
: m_mutex(QMutex::Recursive)
, m_browserService(browserService)
{
m_clients.reserve(1000);
}
QJsonObject BrowserClients::readResponse(const QByteArray& arr)
{
QJsonObject json;
const QJsonObject message = byteArrayToJson(arr);
const QString clientID = getClientID(message);
if (!clientID.isEmpty()) {
const ClientPtr client = getClient(clientID);
if (client->browserAction) {
json = client->browserAction->readResponse(message);
}
}
return json;
}
QJsonObject BrowserClients::byteArrayToJson(const QByteArray& arr) const
{
QJsonObject json;
QJsonParseError err;
QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
if (doc.isObject()) {
json = doc.object();
}
return json;
}
QString BrowserClients::getClientID(const QJsonObject& json) const
{
return json["clientID"].toString();
}
BrowserClients::ClientPtr BrowserClients::getClient(const QString& clientID)
{
QMutexLocker locker(&m_mutex);
for (const auto& i : m_clients) {
if (i->clientID.compare(clientID, Qt::CaseSensitive) == 0) {
return i;
}
}
// clientID not found, create a new client
QSharedPointer<BrowserAction> ba = QSharedPointer<BrowserAction>::create(m_browserService);
ClientPtr client = ClientPtr::create(clientID, ba);
m_clients.push_back(client);
return m_clients.back();
}

View File

@ -1,61 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BROWSERCLIENTS_H
#define BROWSERCLIENTS_H
#include "BrowserAction.h"
#include <QJsonObject>
#include <QLocalSocket>
#include <QMutex>
#include <QSharedPointer>
#include <QVector>
class BrowserClients
{
struct Client
{
Client(const QString& id, QSharedPointer<BrowserAction> ba)
: clientID(id)
, browserAction(ba)
{
}
QString clientID;
QSharedPointer<BrowserAction> browserAction;
};
typedef QSharedPointer<Client> ClientPtr;
public:
BrowserClients(BrowserService& browserService);
~BrowserClients() = default;
QJsonObject readResponse(const QByteArray& arr);
private:
QJsonObject byteArrayToJson(const QByteArray& arr) const;
QString getClientID(const QJsonObject& json) const;
ClientPtr getClient(const QString& clientID);
private:
QMutex m_mutex;
QVector<ClientPtr> m_clients;
BrowserService& m_browserService;
};
#endif // BROWSERCLIENTS_H

107
src/browser/BrowserHost.cpp Normal file
View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2020 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 "BrowserHost.h"
#include "BrowserSettings.h"
#include "BrowserShared.h"
#include <QJsonDocument>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMutexLocker>
#include <QtNetwork>
#include "sodium.h"
#include <iostream>
BrowserHost::BrowserHost(QObject* parent)
: QObject(parent)
{
m_localServer = new QLocalServer(this);
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(proxyConnected()));
}
BrowserHost::~BrowserHost()
{
stop();
}
void BrowserHost::start()
{
if (sodium_init() == -1) {
qWarning() << "Failed to start browser service: libsodium failed to initialize!";
return;
}
if (!m_localServer->isListening()) {
m_localServer->listen(BrowserShared::localServerPath());
}
}
void BrowserHost::stop()
{
m_socketList.clear();
m_localServer->close();
}
void BrowserHost::proxyConnected()
{
auto socket = m_localServer->nextPendingConnection();
if (socket) {
m_socketList.append(socket);
connect(socket, SIGNAL(readyRead()), this, SLOT(readProxyMessage()));
connect(socket, SIGNAL(disconnected()), this, SLOT(proxyDisconnected()));
}
}
void BrowserHost::readProxyMessage()
{
QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
if (!socket || socket->bytesAvailable() <= 0) {
return;
}
socket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
QJsonParseError error;
auto json = QJsonDocument::fromJson(socket->readAll(), &error);
if (json.isNull()) {
qWarning() << "Failed to read proxy message: " << error.errorString();
return;
}
emit clientMessageReceived(json.object());
}
void BrowserHost::sendClientMessage(const QJsonObject& json)
{
QString reply(QJsonDocument(json).toJson(QJsonDocument::Compact));
for (const auto socket : m_socketList) {
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
QByteArray arr = reply.toUtf8();
socket->write(arr.constData(), arr.length());
socket->flush();
}
}
}
void BrowserHost::proxyDisconnected()
{
auto socket = qobject_cast<QLocalSocket*>(QObject::sender());
m_socketList.removeOne(socket);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -18,29 +18,37 @@
#ifndef NATIVEMESSAGINGHOST_H
#define NATIVEMESSAGINGHOST_H
#include "NativeMessagingBase.h"
#include <QJsonObject>
#include <QObject>
#include <QPointer>
class NativeMessagingHost : public NativeMessagingBase
class QLocalServer;
class QLocalSocket;
class BrowserHost : public QObject
{
Q_OBJECT
public:
NativeMessagingHost();
~NativeMessagingHost() override;
explicit BrowserHost(QObject* parent = nullptr);
~BrowserHost() override;
public slots:
void newLocalMessage();
void deleteSocket();
void socketStateChanged(QLocalSocket::LocalSocketState socketState);
void start();
void stop();
void sendClientMessage(const QJsonObject& json);
signals:
void clientMessageReceived(const QJsonObject& json);
private slots:
void proxyConnected();
void readProxyMessage();
void proxyDisconnected();
private:
void readNativeMessages() override;
void readLength() override;
bool readStdIn(const quint32 length) override;
private:
QLocalSocket* m_localSocket;
Q_DISABLE_COPY(NativeMessagingHost)
QPointer<QLocalServer> m_localServer;
QList<QLocalSocket*> m_socketList;
};
#endif // NATIVEMESSAGINGHOST_H

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -26,8 +26,10 @@
#include <QUuid>
#include "BrowserAccessControlDialog.h"
#include "BrowserAction.h"
#include "BrowserEntryConfig.h"
#include "BrowserEntrySaveDialog.h"
#include "BrowserHost.h"
#include "BrowserService.h"
#include "BrowserSettings.h"
#include "core/Database.h"
@ -58,34 +60,45 @@ const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnl
// Multiple URL's
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
BrowserService::BrowserService(DatabaseTabWidget* parent)
: m_dbTabWidget(parent)
Q_GLOBAL_STATIC(BrowserService, s_browserService);
BrowserService::BrowserService()
: QObject()
, m_browserHost(new BrowserHost)
, m_dialogActive(false)
, m_bringToFrontRequested(false)
, m_prevWindowState(WindowState::Normal)
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
{
// Don't connect the signals when used from DatabaseSettingsWidgetBrowser (parent is nullptr)
if (m_dbTabWidget) {
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
connect(
m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
connect(m_dbTabWidget,
SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
this,
SLOT(activateDatabaseChanged(DatabaseWidget*)));
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
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();
}
}
bool BrowserService::isDatabaseOpened() const
{
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
if (!dbWidget) {
return false;
if (m_currentDatabaseWidget) {
return !m_currentDatabaseWidget->isLocked();
}
return dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode;
return false;
}
bool BrowserService::openDatabase(bool triggerUnlock)
@ -94,13 +107,7 @@ bool BrowserService::openDatabase(bool triggerUnlock)
return false;
}
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
if (!dbWidget) {
return false;
}
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
if (m_currentDatabaseWidget && !m_currentDatabaseWidget->isLocked()) {
return true;
}
@ -114,19 +121,20 @@ bool BrowserService::openDatabase(bool triggerUnlock)
void BrowserService::lockDatabase()
{
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
if (m_currentDatabaseWidget) {
m_currentDatabaseWidget->lock();
}
}
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
if (!dbWidget) {
return;
}
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
dbWidget->lock();
QString BrowserService::getDatabaseHash(bool legacy)
{
if (legacy) {
return QCryptographicHash::hash(
(browserService()->getDatabaseRootUuid() + browserService()->getDatabaseRecycleBinUuid()).toUtf8(),
QCryptographicHash::Sha256)
.toHex();
}
return QCryptographicHash::hash(getDatabaseRootUuid().toUtf8(), QCryptographicHash::Sha256).toHex();
}
QString BrowserService::getDatabaseRootUuid()
@ -180,9 +188,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group)
return groupList;
}
QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& selectedDb)
QJsonObject BrowserService::getDatabaseGroups()
{
auto db = selectedDb ? selectedDb : getDatabase();
auto db = getDatabase();
if (!db) {
return {};
}
@ -208,15 +216,6 @@ QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& se
QJsonObject BrowserService::createNewGroup(const QString& groupName)
{
QJsonObject result;
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
"createNewGroup",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QJsonObject, result),
Q_ARG(QString, groupName));
return result;
}
auto db = getDatabase();
if (!db) {
@ -232,6 +231,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
// Group already exists
if (group) {
QJsonObject result;
result["name"] = group->name();
result["uuid"] = Tools::uuidToHex(group->uuid());
return result;
@ -245,7 +245,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
MessageBox::Yes | MessageBox::No);
if (dialogResult != MessageBox::Yes) {
return result;
return {};
}
QString name, uuid;
@ -279,6 +279,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
previousGroup = tempGroup;
}
QJsonObject result;
result["name"] = name;
result["uuid"] = uuid;
return result;
@ -286,25 +287,18 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
QString BrowserService::storeKey(const QString& key)
{
QString id;
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(
this, "storeKey", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, id), Q_ARG(QString, key));
return id;
}
auto db = getDatabase();
if (!db) {
return {};
}
bool contains;
MessageBox::Button dialogResult = MessageBox::Cancel;
auto dialogResult = MessageBox::Cancel;
QString id;
do {
QInputDialog keyDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
connect(m_currentDatabaseWidget, SIGNAL(databaseLocked()), &keyDialog, SLOT(reject()));
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
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.")
@ -353,28 +347,14 @@ QString BrowserService::getKey(const QString& id)
return db->metadata()->customData()->value(ASSOCIATE_KEY_PREFIX + id);
}
QJsonArray BrowserService::findMatchingEntries(const QString& id,
QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
const QString& url,
const QString& submitUrl,
const QString& realm,
const StringPairList& keyList,
const bool httpAuth)
{
QJsonArray result;
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
"findMatchingEntries",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QJsonArray, result),
Q_ARG(QString, id),
Q_ARG(QString, url),
Q_ARG(QString, submitUrl),
Q_ARG(QString, realm),
Q_ARG(StringPairList, keyList),
Q_ARG(bool, httpAuth));
return result;
}
Q_UNUSED(dbid);
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
const QString host = QUrl(url).host();
@ -425,18 +405,19 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
}
if (pwEntries.isEmpty()) {
return QJsonArray();
return {};
}
// Ensure that database is not locked when the popup was visible
if (!isDatabaseOpened()) {
return QJsonArray();
return {};
}
// Sort results
pwEntries = sortEntries(pwEntries, host, submitUrl);
// Fill the list
QJsonArray result;
for (auto* entry : pwEntries) {
result.append(prepareEntry(entry));
}
@ -444,7 +425,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
return result;
}
void BrowserService::addEntry(const QString& id,
void BrowserService::addEntry(const QString& dbid,
const QString& login,
const QString& password,
const QString& url,
@ -454,21 +435,8 @@ void BrowserService::addEntry(const QString& id,
const QString& groupUuid,
const QSharedPointer<Database>& selectedDb)
{
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
"addEntry",
Qt::BlockingQueuedConnection,
Q_ARG(QString, id),
Q_ARG(QString, login),
Q_ARG(QString, password),
Q_ARG(QString, url),
Q_ARG(QString, submitUrl),
Q_ARG(QString, realm),
Q_ARG(QString, group),
Q_ARG(QString, groupUuid),
Q_ARG(QSharedPointer<Database>, selectedDb));
}
// TODO: select database based on this key id
Q_UNUSED(dbid);
auto db = selectedDb ? selectedDb : selectedDatabase();
if (!db) {
return;
@ -510,37 +478,25 @@ void BrowserService::addEntry(const QString& id,
config.save(entry);
}
BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
bool BrowserService::updateEntry(const QString& dbid,
const QString& uuid,
const QString& login,
const QString& password,
const QString& url,
const QString& submitUrl)
{
ReturnValue result = ReturnValue::Error;
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
"updateEntry",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(ReturnValue, result),
Q_ARG(QString, id),
Q_ARG(QString, uuid),
Q_ARG(QString, login),
Q_ARG(QString, password),
Q_ARG(QString, url),
Q_ARG(QString, submitUrl));
}
// TODO: select database based on this key id
Q_UNUSED(dbid);
auto db = selectedDatabase();
if (!db) {
return ReturnValue::Error;
return false;
}
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
if (!entry) {
// If entry is not found for update, add a new one to the selected database
addEntry(id, login, password, url, submitUrl, "", "", "", db);
return ReturnValue::Success;
addEntry(dbid, login, password, url, submitUrl, "", "", "", db);
return true;
}
// Check if the entry password is a reference. If so, update the original entry instead
@ -549,16 +505,17 @@ BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
if (!referenceUuid.isNull()) {
entry = db->rootGroup()->findEntryByUuid(referenceUuid);
if (!entry) {
return ReturnValue::Error;
return false;
}
}
}
QString username = entry->username();
if (username.isEmpty()) {
return ReturnValue::Error;
return false;
}
bool result = false;
if (username.compare(login, Qt::CaseSensitive) != 0
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
MessageBox::Button dialogResult = MessageBox::No;
@ -580,9 +537,7 @@ BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
}
entry->setPassword(password);
entry->endUpdate();
result = ReturnValue::Success;
} else {
result = ReturnValue::Canceled;
result = true;
}
hideWindow();
@ -646,17 +601,14 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& s
// Get the list of databases to search
QList<QSharedPointer<Database>> databases;
if (browserSettings()->searchInAllDatabases()) {
const int count = m_dbTabWidget->count();
for (int i = 0; i < count; ++i) {
if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
if (const auto& db = dbWidget->database()) {
if (databaseConnected(db)) {
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
auto db = dbWidget->database();
if (db && databaseConnected(dbWidget->database())) {
databases << db;
}
}
}
}
} else if (const auto& db = getDatabase()) {
} else {
const auto& db = getDatabase();
if (databaseConnected(db)) {
databases << db;
}
@ -674,9 +626,8 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& s
return entries;
}
void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database>& currentDb)
void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> db)
{
auto db = currentDb ? currentDb : getDatabase();
if (!db) {
return;
}
@ -806,7 +757,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
m_dialogActive = true;
BrowserAccessControlDialog accessControlDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
connect(m_currentDatabaseWidget, SIGNAL(databaseLocked()), &accessControlDialog, SLOT(reject()));
connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) {
auto entry = pwEntriesToConfirm[item->row()];
BrowserEntryConfig config;
@ -1103,10 +1054,8 @@ QString BrowserService::baseDomain(const QString& hostname) const
QSharedPointer<Database> BrowserService::getDatabase()
{
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
if (const auto& db = dbWidget->database()) {
return db;
}
if (m_currentDatabaseWidget) {
return m_currentDatabaseWidget->database();
}
return {};
}
@ -1114,20 +1063,15 @@ QSharedPointer<Database> BrowserService::getDatabase()
QSharedPointer<Database> BrowserService::selectedDatabase()
{
QList<DatabaseWidget*> databaseWidgets;
for (int i = 0;; ++i) {
auto* dbWidget = m_dbTabWidget->databaseWidgetFromIndex(i);
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
// Add only open databases
if (dbWidget && !dbWidget->isLocked()) {
databaseWidgets.push_back(dbWidget);
continue;
if (!dbWidget->isLocked()) {
databaseWidgets << dbWidget;
}
// Break out if dbStruct.dbWidget is nullptr
break;
}
BrowserEntrySaveDialog browserEntrySaveDialog;
int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_dbTabWidget->currentDatabaseWidget());
int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_currentDatabaseWidget);
if (openDatabaseCount > 1) {
int res = browserEntrySaveDialog.exec();
if (res == QDialog::Accepted) {
@ -1145,7 +1089,7 @@ QSharedPointer<Database> BrowserService::selectedDatabase()
return getDatabase();
}
bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name) const
bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
{
if (entry->attributes()->contains(name)) {
QString attr = entry->attributes()->value(name);
@ -1160,7 +1104,7 @@ bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
return false;
}
int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Database>& db) const
int BrowserService::moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db)
{
int keyCounter = 0;
for (const auto& key : entry->attributes()->keys()) {
@ -1179,14 +1123,9 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Data
return keyCounter;
}
bool BrowserService::checkLegacySettings()
bool BrowserService::checkLegacySettings(QSharedPointer<Database> db)
{
if (!browserSettings()->isEnabled() || browserSettings()->noMigrationPrompt()) {
return false;
}
auto db = getDatabase();
if (!db) {
if (!db || !browserSettings()->isEnabled() || browserSettings()->noMigrationPrompt()) {
return false;
}
@ -1272,7 +1211,9 @@ void BrowserService::raiseWindow(const bool force)
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
{
if (dbWidget) {
emit databaseLocked();
QJsonObject msg;
msg["action"] = QString("database-locked");
m_browserHost->sendClientMessage(msg);
}
}
@ -1283,22 +1224,43 @@ void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
hideWindow();
m_bringToFrontRequested = false;
}
emit databaseUnlocked();
if (checkLegacySettings()) {
convertAttributesToCustomData();
QJsonObject msg;
msg["action"] = QString("database-unlocked");
m_browserHost->sendClientMessage(msg);
auto db = dbWidget->database();
if (checkLegacySettings(db)) {
convertAttributesToCustomData(db);
}
}
}
void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
void BrowserService::activeDatabaseChanged(DatabaseWidget* dbWidget)
{
m_currentDatabaseWidget = dbWidget;
if (dbWidget) {
auto currentMode = dbWidget->currentMode();
if (currentMode == DatabaseWidget::Mode::ViewMode || currentMode == DatabaseWidget::Mode::EditMode) {
emit databaseUnlocked();
if (dbWidget->isLocked()) {
databaseLocked(dbWidget);
} else {
emit databaseLocked();
databaseUnlocked(dbWidget);
}
}
}
void BrowserService::processClientMessage(const QJsonObject& message)
{
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);
auto response = action->processClientMessage(message);
m_browserHost->sendClientMessage(response);
}

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -21,8 +21,9 @@
#define BROWSERSERVICE_H
#include "core/Entry.h"
#include "gui/DatabaseTabWidget.h"
#include <QObject>
#include <QPointer>
#include <QSharedPointer>
#include <QtCore>
typedef QPair<QString, QString> StringPair;
@ -33,28 +34,33 @@ enum
max_length = 16 * 1024
};
class DatabaseTabWidget;
class DatabaseWidget;
class BrowserHost;
class BrowserAction;
class BrowserService : public QObject
{
Q_OBJECT
public:
enum ReturnValue
{
Success,
Error,
Canceled
};
explicit BrowserService();
static BrowserService* instance();
explicit BrowserService(DatabaseTabWidget* parent);
void setEnabled(bool enabled);
QString getKey(const QString& id);
QString storeKey(const QString& key);
QString getDatabaseHash(bool legacy = false);
bool isDatabaseOpened() const;
bool openDatabase(bool triggerUnlock);
QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid();
QJsonObject getDatabaseGroups(const QSharedPointer<Database>& selectedDb = {});
void lockDatabase();
QJsonObject getDatabaseGroups();
QJsonObject createNewGroup(const QString& groupName);
QString getKey(const QString& id);
void addEntry(const QString& id,
void addEntry(const QString& dbid,
const QString& login,
const QString& password,
const QString& url,
@ -63,11 +69,22 @@ public:
const QString& group,
const QString& groupUuid,
const QSharedPointer<Database>& selectedDb = {});
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl);
QList<Entry*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
bool updateEntry(const QString& dbid,
const QString& uuid,
const QString& login,
const QString& password,
const QString& url,
const QString& submitUrl);
QJsonArray findMatchingEntries(const QString& dbid,
const QString& url,
const QString& submitUrl,
const QString& realm,
const StringPairList& keyList,
const bool httpAuth = false);
static void convertAttributesToCustomData(QSharedPointer<Database> db);
public:
static const QString KEEPASSXCBROWSER_NAME;
static const QString KEEPASSXCBROWSER_OLD_NAME;
static const QString ASSOCIATE_KEY_PREFIX;
@ -78,28 +95,12 @@ public:
static const QString ADDITIONAL_URL;
public slots:
QJsonArray findMatchingEntries(const QString& id,
const QString& url,
const QString& submitUrl,
const QString& realm,
const StringPairList& keyList,
const bool httpAuth = false);
QString storeKey(const QString& key);
ReturnValue updateEntry(const QString& id,
const QString& uuid,
const QString& login,
const QString& password,
const QString& url,
const QString& submitUrl);
void databaseLocked(DatabaseWidget* dbWidget);
void databaseUnlocked(DatabaseWidget* dbWidget);
void activateDatabaseChanged(DatabaseWidget* dbWidget);
void lockDatabase();
void activeDatabaseChanged(DatabaseWidget* dbWidget);
signals:
void databaseLocked();
void databaseUnlocked();
void databaseChanged();
private slots:
void processClientMessage(const QJsonObject& message);
private:
enum Access
@ -116,7 +117,8 @@ private:
Hidden
};
private:
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl);
QList<Entry*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& url,
@ -125,6 +127,7 @@ private:
const QString& realm,
const bool httpAuth);
QJsonObject prepareEntry(const Entry* entry);
QJsonArray getChildrenFromGroup(Group* group);
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
int
@ -135,21 +138,35 @@ private:
QString baseDomain(const QString& hostname) const;
QSharedPointer<Database> getDatabase();
QSharedPointer<Database> selectedDatabase();
QJsonArray getChildrenFromGroup(Group* group);
bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
int moveKeysToCustomData(Entry* entry, const QSharedPointer<Database>& db) const;
bool checkLegacySettings();
QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid();
bool checkLegacySettings(QSharedPointer<Database> db);
void hideWindow() const;
void raiseWindow(const bool force = false);
private:
DatabaseTabWidget* const m_dbTabWidget;
static bool moveSettingsToCustomData(Entry* entry, const QString& name);
static int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db);
QPointer<BrowserHost> m_browserHost;
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;
bool m_dialogActive;
bool m_bringToFrontRequested;
WindowState m_prevWindowState;
QUuid m_keepassBrowserUUID;
QPointer<DatabaseWidget> m_currentDatabaseWidget;
Q_DISABLE_COPY(BrowserService);
friend class TestBrowser;
};
static inline BrowserService* browserService()
{
return BrowserService::instance();
}
#endif // BROWSERSERVICE_H

View File

@ -162,16 +162,6 @@ void BrowserSettings::setNoMigrationPrompt(bool prompt)
config()->set(Config::Browser_NoMigrationPrompt, prompt);
}
bool BrowserSettings::supportBrowserProxy()
{
return config()->get(Config::Browser_SupportBrowserProxy).toBool();
}
void BrowserSettings::setSupportBrowserProxy(bool enabled)
{
config()->set(Config::Browser_SupportBrowserProxy, enabled);
}
bool BrowserSettings::useCustomProxy()
{
return config()->get(Config::Browser_UseCustomProxy).toBool();
@ -184,9 +174,6 @@ void BrowserSettings::setUseCustomProxy(bool enabled)
QString BrowserSettings::customProxyLocation()
{
if (!useCustomProxy()) {
return QString();
}
return config()->get(Config::Browser_CustomProxyLocation).toString();
}
@ -195,6 +182,11 @@ void BrowserSettings::setCustomProxyLocation(const QString& location)
config()->set(Config::Browser_CustomProxyLocation, location);
}
QString BrowserSettings::proxyLocation()
{
return m_nativeMessageInstaller.getProxyPath();
}
bool BrowserSettings::updateBinaryPath()
{
return config()->get(Config::Browser_UpdateBinaryPath).toBool();
@ -215,81 +207,14 @@ void BrowserSettings::setAllowExpiredCredentials(bool enabled)
config()->set(Config::Browser_AllowExpiredCredentials, enabled);
}
bool BrowserSettings::chromeSupport()
bool BrowserSettings::browserSupport(BrowserShared::SupportedBrowsers browser)
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);
return m_nativeMessageInstaller.isBrowserEnabled(browser);
}
void BrowserSettings::setChromeSupport(bool enabled)
void BrowserSettings::setBrowserSupport(BrowserShared::SupportedBrowsers browser, bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation());
}
bool BrowserSettings::chromiumSupport()
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROMIUM);
}
void BrowserSettings::setChromiumSupport(bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation());
}
bool BrowserSettings::firefoxSupport()
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::FIREFOX);
}
void BrowserSettings::setFirefoxSupport(bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation());
}
bool BrowserSettings::vivaldiSupport()
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::VIVALDI);
}
void BrowserSettings::setVivaldiSupport(bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
}
bool BrowserSettings::braveSupport()
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::BRAVE);
}
void BrowserSettings::setBraveSupport(bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::BRAVE, enabled, supportBrowserProxy(), customProxyLocation());
}
bool BrowserSettings::torBrowserSupport()
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::TOR_BROWSER);
}
void BrowserSettings::setTorBrowserSupport(bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::TOR_BROWSER, enabled, supportBrowserProxy(), customProxyLocation());
}
bool BrowserSettings::edgeSupport()
{
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::EDGE);
}
void BrowserSettings::setEdgeSupport(bool enabled)
{
m_hostInstaller.installBrowser(
HostInstaller::SupportedBrowsers::EDGE, enabled, supportBrowserProxy(), customProxyLocation());
m_nativeMessageInstaller.setBrowserEnabled(browser, enabled);
}
bool BrowserSettings::passwordUseNumbers()
@ -563,13 +488,7 @@ QJsonObject BrowserSettings::generatePassword()
return password;
}
void BrowserSettings::updateBinaryPaths(const QString& customProxyLocation)
void BrowserSettings::updateBinaryPaths()
{
bool isProxy = supportBrowserProxy();
m_hostInstaller.updateBinaryPaths(isProxy, customProxyLocation);
}
bool BrowserSettings::checkIfProxyExists(QString& path)
{
return m_hostInstaller.checkIfProxyExists(supportBrowserProxy(), customProxyLocation(), path);
m_nativeMessageInstaller.updateBinaryPaths();
}

View File

@ -20,7 +20,8 @@
#ifndef BROWSERSETTINGS_H
#define BROWSERSETTINGS_H
#include "HostInstaller.h"
#include "BrowserShared.h"
#include "NativeMessageInstaller.h"
#include "core/PassphraseGenerator.h"
#include "core/PasswordGenerator.h"
@ -58,30 +59,18 @@ public:
bool noMigrationPrompt();
void setNoMigrationPrompt(bool prompt);
bool supportBrowserProxy();
void setSupportBrowserProxy(bool enabled);
bool useCustomProxy();
void setUseCustomProxy(bool enabled);
QString customProxyLocation();
void setCustomProxyLocation(const QString& location);
QString proxyLocation();
bool updateBinaryPath();
void setUpdateBinaryPath(bool enabled);
bool allowExpiredCredentials();
void setAllowExpiredCredentials(bool enabled);
bool chromeSupport();
void setChromeSupport(bool enabled);
bool chromiumSupport();
void setChromiumSupport(bool enabled);
bool firefoxSupport();
void setFirefoxSupport(bool enabled);
bool vivaldiSupport();
void setVivaldiSupport(bool enabled);
bool braveSupport();
void setBraveSupport(bool enabled);
bool torBrowserSupport();
void setTorBrowserSupport(bool enabled);
bool edgeSupport();
void setEdgeSupport(bool enabled);
bool browserSupport(BrowserShared::SupportedBrowsers browser);
void setBrowserSupport(BrowserShared::SupportedBrowsers browser, bool enabled);
bool passwordUseNumbers();
void setPasswordUseNumbers(bool useNumbers);
@ -126,15 +115,14 @@ public:
PasswordGenerator::CharClasses passwordCharClasses();
PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
QJsonObject generatePassword();
void updateBinaryPaths(const QString& customProxyLocation = QString());
bool checkIfProxyExists(QString& path);
void updateBinaryPaths();
private:
static BrowserSettings* m_instance;
PasswordGenerator m_passwordGenerator;
PassphraseGenerator m_passPhraseGenerator;
HostInstaller m_hostInstaller;
NativeMessageInstaller m_nativeMessageInstaller;
};
inline BrowserSettings* browserSettings()

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 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 "BrowserSettingsPage.h"
#include "BrowserService.h"
#include "BrowserSettings.h"
#include "BrowserSettingsWidget.h"
#include "core/Resources.h"
QString BrowserSettingsPage::name()
{
return QObject::tr("Browser Integration");
}
QIcon BrowserSettingsPage::icon()
{
return Resources::instance()->icon("internet-web-browser");
}
QWidget* BrowserSettingsPage::createWidget()
{
return new BrowserSettingsWidget();
}
void BrowserSettingsPage::loadSettings(QWidget* widget)
{
qobject_cast<BrowserSettingsWidget*>(widget)->loadSettings();
}
void BrowserSettingsPage::saveSettings(QWidget* widget)
{
qobject_cast<BrowserSettingsWidget*>(widget)->saveSettings();
browserService()->setEnabled(browserSettings()->isEnabled());
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2020 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_BROWSERSETTINGSPAGE_H
#define KEEPASSXC_BROWSERSETTINGSPAGE_H
#include "gui/ApplicationSettingsWidget.h"
class BrowserSettingsPage : public ISettingsPage
{
public:
explicit BrowserSettingsPage() = default;
~BrowserSettingsPage() override = default;
QString name() override;
QIcon icon() override;
QWidget* createWidget() override;
void loadSettings(QWidget* widget) override;
void saveSettings(QWidget* widget) override;
};
#endif // KEEPASSXC_BROWSERSETTINGSPAGE_H

View File

@ -1,7 +1,5 @@
/*
* Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -17,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BrowserOptionDialog.h"
#include "ui_BrowserOptionDialog.h"
#include "BrowserSettingsWidget.h"
#include "ui_BrowserSettingsWidget.h"
#include "BrowserSettings.h"
#include "config-keepassx.h"
@ -26,9 +24,9 @@
#include <QFileDialog>
BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
BrowserSettingsWidget::BrowserSettingsWidget(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::BrowserOptionDialog())
, m_ui(new Ui::BrowserSettingsWidget())
{
m_ui->setupUi(this);
@ -52,13 +50,9 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
snapInstructions));
// clang-format on
m_ui->scriptWarningWidget->setVisible(false);
m_ui->scriptWarningWidget->setAutoHideTimeout(-1);
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
MessageWidget::Warning);
m_ui->warningWidget->setCloseButtonVisible(false);
m_ui->warningWidget->setAutoHideTimeout(-1);
m_ui->warningWidget->setAnimate(false);
m_ui->tabWidget->setEnabled(m_ui->enableBrowserSupport->isChecked());
connect(m_ui->enableBrowserSupport, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool)));
@ -67,6 +61,8 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
m_ui->customProxyLocationBrowseButton->setEnabled(m_ui->useCustomProxy->isChecked());
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocation, SLOT(setEnabled(bool)));
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocationBrowseButton, SLOT(setEnabled(bool)));
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), SLOT(validateCustomProxyLocation()));
connect(m_ui->customProxyLocation, SIGNAL(editingFinished()), SLOT(validateCustomProxyLocation()));
connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
#ifndef Q_OS_LINUX
@ -86,11 +82,11 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
m_ui->browserGlobalWarningWidget->setVisible(false);
}
BrowserOptionDialog::~BrowserOptionDialog()
BrowserSettingsWidget::~BrowserSettingsWidget()
{
}
void BrowserOptionDialog::loadSettings()
void BrowserSettingsWidget::loadSettings()
{
auto settings = browserSettings();
m_ui->enableBrowserSupport->setChecked(settings->isEnabled());
@ -116,43 +112,39 @@ void BrowserOptionDialog::loadSettings()
m_ui->searchInAllDatabases->setChecked(settings->searchInAllDatabases());
m_ui->supportKphFields->setChecked(settings->supportKphFields());
m_ui->noMigrationPrompt->setChecked(settings->noMigrationPrompt());
m_ui->supportBrowserProxy->setChecked(settings->supportBrowserProxy());
m_ui->useCustomProxy->setChecked(settings->useCustomProxy());
m_ui->customProxyLocation->setText(settings->customProxyLocation());
m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath());
m_ui->allowExpiredCredentials->setChecked(settings->allowExpiredCredentials());
m_ui->chromeSupport->setChecked(settings->chromeSupport());
m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
m_ui->edgeSupport->setChecked(settings->edgeSupport());
m_ui->chromeSupport->setChecked(settings->browserSupport(BrowserShared::CHROME));
m_ui->chromiumSupport->setChecked(settings->browserSupport(BrowserShared::CHROMIUM));
m_ui->firefoxSupport->setChecked(settings->browserSupport(BrowserShared::FIREFOX));
m_ui->edgeSupport->setChecked(settings->browserSupport(BrowserShared::EDGE));
#ifndef Q_OS_WIN
m_ui->braveSupport->setChecked(settings->braveSupport());
m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport());
m_ui->torBrowserSupport->setChecked(settings->torBrowserSupport());
m_ui->braveSupport->setChecked(settings->browserSupport(BrowserShared::BRAVE));
m_ui->vivaldiSupport->setChecked(settings->browserSupport(BrowserShared::VIVALDI));
m_ui->torBrowserSupport->setChecked(settings->browserSupport(BrowserShared::TOR_BROWSER));
#endif
#ifndef Q_OS_LINUX
m_ui->snapWarningLabel->setVisible(false);
#endif
// TODO: Enable when Linux version is released
// TODO: Enable Edge support when Linux version is released
#ifdef Q_OS_LINUX
m_ui->edgeSupport->setChecked(false);
m_ui->edgeSupport->setEnabled(false);
#endif
#if defined(KEEPASSXC_DIST_APPIMAGE)
m_ui->supportBrowserProxy->setChecked(true);
m_ui->supportBrowserProxy->setEnabled(false);
#elif defined(KEEPASSXC_DIST_SNAP)
#ifdef KEEPASSXC_DIST_SNAP
// Disable settings that will not work
m_ui->supportBrowserProxy->setChecked(true);
m_ui->supportBrowserProxy->setEnabled(false);
m_ui->useCustomProxy->setChecked(false);
m_ui->useCustomProxy->setEnabled(false);
m_ui->useCustomProxy->setVisible(false);
m_ui->customProxyLocation->setVisible(false);
m_ui->customProxyLocationBrowseButton->setVisible(false);
m_ui->browsersGroupBox->setVisible(false);
m_ui->browsersGroupBox->setEnabled(false);
m_ui->updateBinaryPath->setChecked(false);
m_ui->updateBinaryPath->setEnabled(false);
m_ui->updateBinaryPath->setVisible(false);
// Show notice to user
m_ui->browserGlobalWarningWidget->showMessage(tr("Please see special instructions for browser extension use below"),
MessageWidget::Warning);
@ -160,23 +152,23 @@ void BrowserOptionDialog::loadSettings()
m_ui->browserGlobalWarningWidget->setAutoHideTimeout(-1);
#endif
// Check for native messaging host location errors
QString path;
if (!settings->checkIfProxyExists(path)) {
auto text =
tr("<b>Warning</b>, the keepassxc-proxy application was not found!"
"<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options."
"<br />Browser integration WILL NOT WORK without the proxy application."
"<br />Expected Path: %1")
.arg(path);
m_ui->scriptWarningWidget->showMessage(text, MessageWidget::Warning);
m_ui->scriptWarningWidget->setVisible(true);
validateCustomProxyLocation();
}
void BrowserSettingsWidget::validateCustomProxyLocation()
{
auto path = m_ui->customProxyLocation->text();
if (m_ui->useCustomProxy->isChecked() && !QFile::exists(path)) {
m_ui->warningWidget->showMessage(tr("<b>Error:</b> The custom proxy location cannot be found!"
"<br/>Browser integration WILL NOT WORK without the proxy application."),
MessageWidget::Error);
} else {
m_ui->scriptWarningWidget->setVisible(false);
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
MessageWidget::Warning);
}
}
void BrowserOptionDialog::saveSettings()
void BrowserSettingsWidget::saveSettings()
{
auto settings = browserSettings();
settings->setEnabled(m_ui->enableBrowserSupport->isChecked());
@ -186,7 +178,6 @@ void BrowserOptionDialog::saveSettings()
settings->setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
settings->setSortByUsername(m_ui->sortByUsername->isChecked());
settings->setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
settings->setUseCustomProxy(m_ui->useCustomProxy->isChecked());
settings->setCustomProxyLocation(m_ui->customProxyLocation->text());
@ -199,18 +190,18 @@ void BrowserOptionDialog::saveSettings()
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
settings->setNoMigrationPrompt(m_ui->noMigrationPrompt->isChecked());
settings->setChromeSupport(m_ui->chromeSupport->isChecked());
settings->setChromiumSupport(m_ui->chromiumSupport->isChecked());
settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked());
settings->setEdgeSupport(m_ui->edgeSupport->isChecked());
settings->setBrowserSupport(BrowserShared::CHROME, m_ui->chromeSupport->isChecked());
settings->setBrowserSupport(BrowserShared::CHROMIUM, m_ui->chromiumSupport->isChecked());
settings->setBrowserSupport(BrowserShared::FIREFOX, m_ui->firefoxSupport->isChecked());
settings->setBrowserSupport(BrowserShared::EDGE, m_ui->edgeSupport->isChecked());
#ifndef Q_OS_WIN
settings->setBraveSupport(m_ui->braveSupport->isChecked());
settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
settings->setTorBrowserSupport(m_ui->torBrowserSupport->isChecked());
settings->setBrowserSupport(BrowserShared::BRAVE, m_ui->braveSupport->isChecked());
settings->setBrowserSupport(BrowserShared::VIVALDI, m_ui->vivaldiSupport->isChecked());
settings->setBrowserSupport(BrowserShared::TOR_BROWSER, m_ui->torBrowserSupport->isChecked());
#endif
}
void BrowserOptionDialog::showProxyLocationFileDialog()
void BrowserSettingsWidget::showProxyLocationFileDialog()
{
#ifdef Q_OS_WIN
QString fileTypeFilter(QString("%1 (*.exe);;%2 (*.*)").arg(tr("Executable Files"), tr("All Files")));
@ -222,4 +213,5 @@ void BrowserOptionDialog::showProxyLocationFileDialog()
QFileInfo(QCoreApplication::applicationDirPath()).filePath(),
fileTypeFilter);
m_ui->customProxyLocation->setText(proxyLocation);
validateCustomProxyLocation();
}

View File

@ -1,7 +1,5 @@
/*
* Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -17,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BROWSEROPTIONDIALOG_H
#define BROWSEROPTIONDIALOG_H
#ifndef BROWSERSETTINGSWIDGET_H
#define BROWSERSETTINGSWIDGET_H
#include <QPointer>
#include <QScopedPointer>
@ -26,16 +24,16 @@
namespace Ui
{
class BrowserOptionDialog;
class BrowserSettingsWidget;
}
class BrowserOptionDialog : public QWidget
class BrowserSettingsWidget : public QWidget
{
Q_OBJECT
public:
explicit BrowserOptionDialog(QWidget* parent = nullptr);
~BrowserOptionDialog();
explicit BrowserSettingsWidget(QWidget* parent = nullptr);
~BrowserSettingsWidget();
public slots:
void loadSettings();
@ -43,9 +41,10 @@ public slots:
private slots:
void showProxyLocationFileDialog();
void validateCustomProxyLocation();
private:
QScopedPointer<Ui::BrowserOptionDialog> m_ui;
QScopedPointer<Ui::BrowserSettingsWidget> m_ui;
};
#endif // BROWSEROPTIONDIALOG_H
#endif // BROWSERSETTINGSWIDGET_H

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BrowserOptionDialog</class>
<widget class="QWidget" name="BrowserOptionDialog">
<class>BrowserSettingsWidget</class>
<widget class="QWidget" name="BrowserSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
@ -49,16 +49,6 @@
<string>General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="MessageWidget" name="scriptWarningWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="snapWarningLabel">
<property name="text">
@ -351,16 +341,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="supportBrowserProxy">
<property name="toolTip">
<string>Support a proxy application between KeePassXC and browser extension.</string>
</property>
<property name="text">
<string>Use a proxy application between KeePassXC and browser extension</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useCustomProxy">
<property name="toolTip">

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 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 "config-keepassx.h"
#include <QCoreApplication>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QVariant>
namespace BrowserShared
{
QString localServerPath()
{
const auto appName = qApp->property("KPXC_QUALIFIED_APPNAME").toString();
const auto serverName = QStringLiteral("/%1.BrowserServer").arg(appName);
#if defined(KEEPASSXC_DIST_SNAP)
return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverName;
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// Use XDG_RUNTIME_DIR instead of /tmp if it's available
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
return path.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverName
: path + serverName;
#elif defined(Q_OS_WIN)
// Windows uses named pipes
return serverName;
#else // Q_OS_MACOS and others
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverName;
#endif
}
} // namespace BrowserShared

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 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_BROWSERSHARED_H
#define KEEPASSXC_BROWSERSHARED_H
#include <QString>
namespace BrowserShared
{
constexpr int NATIVEMSG_MAX_LENGTH = 1024 * 1024;
enum SupportedBrowsers : int
{
CHROME = 0,
CHROMIUM,
FIREFOX,
VIVALDI,
TOR_BROWSER,
BRAVE,
EDGE,
MAX_SUPPORTED
};
QString localServerPath();
} // namespace BrowserShared
#endif // KEEPASSXC_BROWSERSHARED_H

View File

@ -20,15 +20,15 @@ if(WITH_XC_BROWSER)
set(keepassxcbrowser_SOURCES
BrowserAccessControlDialog.cpp
BrowserAction.cpp
BrowserClients.cpp
BrowserEntryConfig.cpp
BrowserEntrySaveDialog.cpp
BrowserOptionDialog.cpp
BrowserHost.cpp
BrowserSettingsPage.cpp
BrowserSettingsWidget.cpp
BrowserService.cpp
BrowserSettings.cpp
HostInstaller.cpp
NativeMessagingBase.cpp
NativeMessagingHost.cpp
BrowserShared.cpp
NativeMessageInstaller.cpp
Variant.cpp)
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})

View File

@ -1,359 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HostInstaller.h"
#include "config-keepassx.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMessageBox>
#include <QProcessEnvironment>
#include <QStandardPaths>
HostInstaller::HostInstaller()
: HOST_NAME("org.keepassxc.keepassxc_browser")
, ALLOWED_EXTENSIONS(QStringList() << "keepassxc-browser@keepassxc.org")
, ALLOWED_ORIGINS(QStringList() << "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"
<< "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/")
#if defined(Q_OS_MACOS)
, TARGET_DIR_CHROME("/Library/Application Support/Google/Chrome/NativeMessagingHosts")
, TARGET_DIR_CHROMIUM("/Library/Application Support/Chromium/NativeMessagingHosts")
, TARGET_DIR_FIREFOX("/Library/Application Support/Mozilla/NativeMessagingHosts")
, TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts")
, TARGET_DIR_TOR_BROWSER("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts")
, TARGET_DIR_BRAVE("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts")
, TARGET_DIR_EDGE("/Library/Application Support/Microsoft Edge/NativeMessagingHosts")
#elif defined(Q_OS_WIN)
// clang-format off
, TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
, TARGET_DIR_CHROMIUM("HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
// clang-format on
, TARGET_DIR_FIREFOX("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
, TARGET_DIR_VIVALDI(TARGET_DIR_CHROME)
, TARGET_DIR_TOR_BROWSER(TARGET_DIR_FIREFOX)
, TARGET_DIR_BRAVE(TARGET_DIR_CHROME)
, TARGET_DIR_EDGE(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
#else
, TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts")
, TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts")
, TARGET_DIR_FIREFOX("/.mozilla/native-messaging-hosts")
, TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts")
, TARGET_DIR_TOR_BROWSER("/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts")
, TARGET_DIR_BRAVE("/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts")
, TARGET_DIR_EDGE("/.config/microsoftedge/NativeMessagingHosts")
#endif
{
}
/**
* Checks if the selected browser has native messaging host properly installed
*
* @param browser Selected browser
* @return bool Script is installed correctly
*/
bool HostInstaller::checkIfInstalled(SupportedBrowsers browser)
{
QString fileName = getPath(browser);
#ifdef Q_OS_WIN
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
return registryEntryFound(settings);
#else
return QFile::exists(fileName);
#endif
}
/**
* Checks if keepassxc-proxy location is found
*
* @param proxy Is keepassxc-proxy enabled
* @param location Custom proxy location
* @param path The path is set here and returned to the caller
* @return bool
*/
bool HostInstaller::checkIfProxyExists(const bool& proxy, const QString& location, QString& path) const
{
QString fileName = getProxyPath(proxy, location);
path = fileName;
return QFile::exists(fileName);
}
/**
* Installs native messaging JSON script for the selected browser
*
* @param browser Selected browser
* @param enabled Is browser integration enabled
* @param proxy Is keepassxc-proxy enabled
* @param location Custom proxy location
*/
void HostInstaller::installBrowser(SupportedBrowsers browser,
const bool& enabled,
const bool& proxy,
const QString& location)
{
if (enabled) {
#ifdef Q_OS_WIN
// Create a registry key
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
settings.setValue("Default", getPath(browser));
#endif
// Always create the script file
QJsonObject script = constructFile(browser, proxy, location);
if (!saveFile(browser, script)) {
QMessageBox::critical(nullptr,
tr("KeePassXC: Cannot save file!"),
tr("Cannot save the native messaging script file."),
QMessageBox::Ok);
}
} else {
// Remove the script file
QString fileName = getPath(browser);
QFile::remove(fileName);
#ifdef Q_OS_WIN
// Remove the registry entry
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
settings.remove("Default");
#endif
}
}
/**
* Updates the paths to native messaging host for each browser that has been enabled
*
* @param proxy Is keepassxc-proxy enabled
* @param location Custom proxy location
*/
void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
{
for (int i = 0; i <= SupportedBrowsers::EDGE; ++i) {
if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
}
}
}
/**
* Returns the target path for each browser. Windows uses a registry path instead of a file path
*
* @param browser Selected browser
* @return QString Current target path for the selected browser
*/
QString HostInstaller::getTargetPath(SupportedBrowsers browser) const
{
switch (browser) {
case SupportedBrowsers::CHROME:
return TARGET_DIR_CHROME;
case SupportedBrowsers::CHROMIUM:
return TARGET_DIR_CHROMIUM;
case SupportedBrowsers::FIREFOX:
return TARGET_DIR_FIREFOX;
case SupportedBrowsers::VIVALDI:
return TARGET_DIR_VIVALDI;
case SupportedBrowsers::TOR_BROWSER:
return TARGET_DIR_TOR_BROWSER;
case SupportedBrowsers::BRAVE:
return TARGET_DIR_BRAVE;
case SupportedBrowsers::EDGE:
return TARGET_DIR_EDGE;
default:
return QString();
}
}
/**
* Returns the browser name
* Needed for Windows to separate Chromium- or Firefox-based scripts
*
* @param browser Selected browser
* @return QString Name of the selected browser
*/
QString HostInstaller::getBrowserName(SupportedBrowsers browser) const
{
switch (browser) {
case SupportedBrowsers::CHROME:
return "chrome";
case SupportedBrowsers::CHROMIUM:
return "chromium";
case SupportedBrowsers::FIREFOX:
return "firefox";
case SupportedBrowsers::VIVALDI:
return "vivaldi";
case SupportedBrowsers::TOR_BROWSER:
return "tor-browser";
case SupportedBrowsers::BRAVE:
return "brave";
case SupportedBrowsers::EDGE:
return "edge";
default:
return QString();
}
}
/**
* Returns the path of native messaging JSON script for the selected browser
*
* @param browser Selected browser
* @return QString JSON script path for the selected browser
*/
QString HostInstaller::getPath(SupportedBrowsers browser) const
{
#ifdef Q_OS_WIN
// If portable settings file exists save the JSON scripts to application folder
QString userPath;
QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini";
if (QFile::exists(portablePath)) {
userPath = QCoreApplication::applicationDirPath();
} else {
userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
}
QString winPath = QString("%1/%2_%3.json").arg(userPath, HOST_NAME, getBrowserName(browser));
winPath.replace("/", "\\");
return winPath;
#else
QString path = getTargetPath(browser);
return QString("%1%2/%3.json").arg(QDir::homePath(), path, HOST_NAME);
#endif
}
/**
* Gets the installation directory for JSON script file (application install path)
*
* @param browser Selected browser
* @return QString Install path
*/
QString HostInstaller::getInstallDir(SupportedBrowsers browser) const
{
QString path = getTargetPath(browser);
#ifdef Q_OS_WIN
return QCoreApplication::applicationDirPath();
#else
return QString("%1%2").arg(QDir::homePath(), path);
#endif
}
/**
* Gets the path to keepassxc-proxy binary
*
* @param proxy Is keepassxc-proxy used with KeePassXC
* @param location Custom proxy path
* @return path Path to keepassxc-proxy
*/
QString HostInstaller::getProxyPath(const bool& proxy, const QString& location) const
{
QString path;
#ifdef KEEPASSXC_DIST_APPIMAGE
if (proxy && !location.isEmpty()) {
path = location;
} else {
path = QProcessEnvironment::systemEnvironment().value("APPIMAGE");
}
#else
if (proxy) {
if (!location.isEmpty()) {
path = location;
} else {
path = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath();
path.append("/keepassxc-proxy");
#ifdef Q_OS_WIN
path.append(".exe");
#endif
}
} else {
path = QFileInfo(QCoreApplication::applicationFilePath()).absoluteFilePath();
}
#ifdef Q_OS_WIN
path.replace("/", "\\");
#endif
#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
return path;
}
/**
* Constructs the JSON script file used with native messaging
*
* @param browser Browser (Chromium- and Firefox-based browsers need a different parameters for the script)
* @param proxy Is keepassxc-proxy used with KeePassXC
* @param location Custom proxy location
* @return script The JSON script file
*/
QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location)
{
QString path = getProxyPath(proxy, location);
QJsonObject script;
script["name"] = HOST_NAME;
script["description"] = QString("KeePassXC integration with native messaging support");
script["path"] = path;
script["type"] = QString("stdio");
QJsonArray arr;
if (browser == SupportedBrowsers::FIREFOX || browser == SupportedBrowsers::TOR_BROWSER) {
for (const QString& extension : ALLOWED_EXTENSIONS) {
arr.append(extension);
}
script["allowed_extensions"] = arr;
} else {
for (const QString& origin : ALLOWED_ORIGINS) {
arr.append(origin);
}
script["allowed_origins"] = arr;
}
return script;
}
/**
* Checks if a registry setting is found with default value
*
* @param settings Registry path
* @return bool Is the registry value found
*/
bool HostInstaller::registryEntryFound(const QSettings& settings)
{
return !settings.value("Default").isNull();
}
/**
* Saves a JSON script file
*
* @param browser Selected browser
* @param script JSON native messaging script object
* @return bool Write succeeds
*/
bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& script)
{
QString path = getPath(browser);
QString installDir = getInstallDir(browser);
QDir dir(installDir);
if (!dir.exists()) {
QDir().mkpath(installDir);
}
QFile scriptFile(path);
if (!scriptFile.open(QIODevice::WriteOnly)) {
return false;
}
QJsonDocument doc(script);
return scriptFile.write(doc.toJson()) >= 0;
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HOSTINSTALLER_H
#define HOSTINSTALLER_H
#include <QJsonObject>
#include <QObject>
#include <QSettings>
class HostInstaller : public QObject
{
Q_OBJECT
public:
enum SupportedBrowsers : int
{
CHROME = 0,
CHROMIUM = 1,
FIREFOX = 2,
VIVALDI = 3,
TOR_BROWSER = 4,
BRAVE = 5,
EDGE = 6
};
public:
HostInstaller();
bool checkIfInstalled(SupportedBrowsers browser);
bool checkIfProxyExists(const bool& proxy, const QString& location, QString& path) const;
void installBrowser(SupportedBrowsers browser,
const bool& enabled,
const bool& proxy = false,
const QString& location = "");
void updateBinaryPaths(const bool& proxy, const QString& location = "");
private:
QString getTargetPath(SupportedBrowsers browser) const;
QString getBrowserName(SupportedBrowsers browser) const;
QString getPath(SupportedBrowsers browser) const;
QString getInstallDir(SupportedBrowsers browser) const;
QString getProxyPath(const bool& proxy, const QString& location) const;
QJsonObject constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location);
bool registryEntryFound(const QSettings& settings);
bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
private:
const QString HOST_NAME;
const QStringList ALLOWED_EXTENSIONS;
const QStringList ALLOWED_ORIGINS;
const QString TARGET_DIR_CHROME;
const QString TARGET_DIR_CHROMIUM;
const QString TARGET_DIR_FIREFOX;
const QString TARGET_DIR_VIVALDI;
const QString TARGET_DIR_TOR_BROWSER;
const QString TARGET_DIR_BRAVE;
const QString TARGET_DIR_EDGE;
};
#endif // HOSTINSTALLER_H

View File

@ -0,0 +1,313 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NativeMessageInstaller.h"
#include "BrowserSettings.h"
#include "config-keepassx.h"
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMessageBox>
#include <QProcessEnvironment>
#include <QSettings>
#include <QStandardPaths>
using namespace BrowserShared;
namespace
{
const QString HOST_NAME = QStringLiteral("org.keepassxc.keepassxc_browser");
const QStringList ALLOWED_EXTENSIONS = QStringList() << QStringLiteral("keepassxc-browser@keepassxc.org");
const QStringList ALLOWED_ORIGINS = QStringList()
<< QStringLiteral("chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/")
<< QStringLiteral("chrome-extension://oboonakemofpalcgghocfoadofidjkkk/");
#if defined(Q_OS_MACOS)
const QString TARGET_DIR_CHROME = QStringLiteral("/Library/Application Support/Google/Chrome/NativeMessagingHosts");
const QString TARGET_DIR_CHROMIUM = QStringLiteral("/Library/Application Support/Chromium/NativeMessagingHosts");
const QString TARGET_DIR_FIREFOX = QStringLiteral("/Library/Application Support/Mozilla/NativeMessagingHosts");
const QString TARGET_DIR_VIVALDI = QStringLiteral("/Library/Application Support/Vivaldi/NativeMessagingHosts");
const QString TARGET_DIR_TOR_BROWSER =
QStringLiteral("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts");
const QString TARGET_DIR_BRAVE =
QStringLiteral("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts");
const QString TARGET_DIR_EDGE = QStringLiteral("/Library/Application Support/Microsoft Edge/NativeMessagingHosts");
#elif defined(Q_OS_WIN)
const QString TARGET_DIR_CHROME = QStringLiteral(
"HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
const QString TARGET_DIR_CHROMIUM =
QStringLiteral("HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
const QString TARGET_DIR_FIREFOX =
QStringLiteral("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
const QString TARGET_DIR_VIVALDI = TARGET_DIR_CHROME;
const QString TARGET_DIR_TOR_BROWSER = TARGET_DIR_FIREFOX;
const QString TARGET_DIR_BRAVE = TARGET_DIR_CHROME;
const QString TARGET_DIR_EDGE = QStringLiteral(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
#else
const QString TARGET_DIR_CHROME = QStringLiteral("/google-chrome/NativeMessagingHosts");
const QString TARGET_DIR_CHROMIUM = QStringLiteral("/chromium/NativeMessagingHosts");
const QString TARGET_DIR_FIREFOX = QStringLiteral("/.mozilla/native-messaging-hosts");
const QString TARGET_DIR_VIVALDI = QStringLiteral("/vivaldi/NativeMessagingHosts");
const QString TARGET_DIR_TOR_BROWSER = QStringLiteral(
"/torbrowser/tbb/x86_64/tor-browser_en-US/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts");
const QString TARGET_DIR_BRAVE = QStringLiteral("/BraveSoftware/Brave-Browser/NativeMessagingHosts");
const QString TARGET_DIR_EDGE = QStringLiteral("/microsoftedge/NativeMessagingHosts");
#endif
} // namespace
/**
* Checks if the selected browser has native messaging host properly installed
*
* @param browser Selected browser
* @return bool Script is installed correctly
*/
bool NativeMessageInstaller::isBrowserEnabled(SupportedBrowsers browser)
{
#ifdef Q_OS_WIN
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
return !settings.value("Default").isNull();
#else
return QFile::exists(getNativeMessagePath(browser));
#endif
}
/**
* Installs native messaging JSON script for the selected browser
*
* @param browser Selected browser
* @param enabled Is browser integration enabled
*/
void NativeMessageInstaller::setBrowserEnabled(SupportedBrowsers browser, bool enabled)
{
if (enabled) {
#ifdef Q_OS_WIN
// Create a registry key
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
settings.setValue("Default", getNativeMessagePath(browser));
#endif
// Always create the script file
if (!createNativeMessageFile(browser)) {
QMessageBox::critical(
nullptr,
QObject::tr("Browser Plugin Failure"),
QObject::tr("Could not save the native messaging script file for %1.").arg(getBrowserName(browser)),
QMessageBox::Ok);
}
} else {
// Remove the script file
QString fileName = getNativeMessagePath(browser);
QFile::remove(fileName);
#ifdef Q_OS_WIN
// Remove the registry entry
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
settings.remove("Default");
#endif
}
}
/**
* Updates the paths to native messaging host for each browser that has been enabled
*/
void NativeMessageInstaller::updateBinaryPaths()
{
for (int i = 0; i < SupportedBrowsers::MAX_SUPPORTED; ++i) {
if (isBrowserEnabled(static_cast<SupportedBrowsers>(i))) {
setBrowserEnabled(static_cast<SupportedBrowsers>(i), true);
}
}
}
/**
* Returns the target path for each browser. Windows uses a registry path instead of a file path
*
* @param browser Selected browser
* @return QString Current target path for the selected browser
*/
QString NativeMessageInstaller::getTargetPath(SupportedBrowsers browser) const
{
switch (browser) {
case SupportedBrowsers::CHROME:
return TARGET_DIR_CHROME;
case SupportedBrowsers::CHROMIUM:
return TARGET_DIR_CHROMIUM;
case SupportedBrowsers::FIREFOX:
return TARGET_DIR_FIREFOX;
case SupportedBrowsers::VIVALDI:
return TARGET_DIR_VIVALDI;
case SupportedBrowsers::TOR_BROWSER:
return TARGET_DIR_TOR_BROWSER;
case SupportedBrowsers::BRAVE:
return TARGET_DIR_BRAVE;
case SupportedBrowsers::EDGE:
return TARGET_DIR_EDGE;
default:
return {};
}
}
/**
* Returns the browser name
* Needed for Windows to separate Chromium- or Firefox-based scripts
*
* @param browser Selected browser
* @return QString Name of the selected browser
*/
QString NativeMessageInstaller::getBrowserName(SupportedBrowsers browser) const
{
switch (browser) {
case SupportedBrowsers::CHROME:
return QStringLiteral("chrome");
case SupportedBrowsers::CHROMIUM:
return QStringLiteral("chromium");
case SupportedBrowsers::FIREFOX:
return QStringLiteral("firefox");
case SupportedBrowsers::VIVALDI:
return QStringLiteral("vivaldi");
case SupportedBrowsers::TOR_BROWSER:
return QStringLiteral("tor-browser");
case SupportedBrowsers::BRAVE:
return QStringLiteral("brave");
case SupportedBrowsers::EDGE:
return QStringLiteral("edge");
default:
return {};
}
}
/**
* Returns the path of native messaging JSON script for the selected browser
*
* @param browser Selected browser
* @return QString JSON script path for the selected browser
*/
QString NativeMessageInstaller::getNativeMessagePath(SupportedBrowsers browser) const
{
QString basePath;
#if defined(Q_OS_WIN)
// If portable settings file exists save the JSON scripts to the application folder
if (QFile::exists(QCoreApplication::applicationDirPath() + QStringLiteral("/keepassxc.ini"))) {
basePath = QCoreApplication::applicationDirPath();
} else {
basePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
}
return QStringLiteral("%1/%2_%3.json").arg(basePath, HOST_NAME, getBrowserName(browser));
#elif defined(Q_OS_LINUX)
if (browser == SupportedBrowsers::TOR_BROWSER) {
// Tor Browser launcher stores its config in ~/.local/share/...
basePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
} else if (browser == SupportedBrowsers::FIREFOX) {
// Firefox stores its config in ~/
basePath = QDir::homePath();
} else {
basePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
}
#else
basePath = QDir::homePath();
#endif
return QStringLiteral("%1%2/%3.json").arg(basePath, getTargetPath(browser), HOST_NAME);
}
/**
* Gets the path to keepassxc-proxy binary
*
* @param location Custom proxy path
* @return path Path to keepassxc-proxy
*/
QString NativeMessageInstaller::getProxyPath() const
{
if (browserSettings()->useCustomProxy()) {
return browserSettings()->customProxyLocation();
}
QString path;
#ifdef KEEPASSXC_DIST_APPIMAGE
path = QProcessEnvironment::systemEnvironment().value("APPIMAGE");
#else
path = QCoreApplication::applicationDirPath() + QStringLiteral("/keepassxc-proxy");
#ifdef Q_OS_WIN
path.append(QStringLiteral(".exe"));
#endif // #ifdef Q_OS_WIN
#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
return QDir::toNativeSeparators(path);
}
/**
* Constructs the JSON script file used with native messaging
*
* @param browser Browser (Chromium- and Firefox-based browsers need a different parameters for the script)
* @param location Custom proxy location
* @return script The JSON script file
*/
QJsonObject NativeMessageInstaller::constructFile(SupportedBrowsers browser)
{
QJsonObject script;
script["name"] = HOST_NAME;
script["description"] = QStringLiteral("KeePassXC integration with native messaging support");
script["path"] = getProxyPath();
script["type"] = QStringLiteral("stdio");
QJsonArray arr;
if (browser == SupportedBrowsers::FIREFOX || browser == SupportedBrowsers::TOR_BROWSER) {
for (const QString& extension : ALLOWED_EXTENSIONS) {
arr.append(extension);
}
script["allowed_extensions"] = arr;
} else {
for (const QString& origin : ALLOWED_ORIGINS) {
arr.append(origin);
}
script["allowed_origins"] = arr;
}
return script;
}
/**
* Saves a JSON script file
*
* @param browser Selected browser
* @param script JSON native messaging script object
* @return bool Write succeeds
*/
bool NativeMessageInstaller::createNativeMessageFile(SupportedBrowsers browser)
{
auto path = getNativeMessagePath(browser);
// Make the parent directory path if necessary
QDir().mkpath(QFileInfo(path).absolutePath());
QFile scriptFile(path);
if (!scriptFile.open(QIODevice::WriteOnly)) {
qWarning() << "Browser Plugin: Failed to open native message file for writing at " << scriptFile.fileName();
qWarning() << scriptFile.errorString();
return false;
}
QJsonDocument doc(constructFile(browser));
if (scriptFile.write(doc.toJson()) < 0) {
qWarning() << "Browser Plugin: Failed to write native message file at " << scriptFile.fileName();
qWarning() << scriptFile.errorString();
return false;
}
return true;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NATIVEMESSAGEINSTALLER_H
#define NATIVEMESSAGEINSTALLER_H
#include "BrowserShared.h"
#include <QJsonObject>
class NativeMessageInstaller
{
public:
NativeMessageInstaller() = default;
void setBrowserEnabled(BrowserShared::SupportedBrowsers browser, bool enabled);
bool isBrowserEnabled(BrowserShared::SupportedBrowsers browser);
QString getProxyPath() const;
void updateBinaryPaths();
private:
QString getTargetPath(BrowserShared::SupportedBrowsers browser) const;
QString getBrowserName(BrowserShared::SupportedBrowsers browser) const;
QString getNativeMessagePath(BrowserShared::SupportedBrowsers browser) const;
QJsonObject constructFile(BrowserShared::SupportedBrowsers browser);
bool createNativeMessageFile(BrowserShared::SupportedBrowsers browser);
Q_DISABLE_COPY(NativeMessageInstaller);
};
#endif // NATIVEMESSAGEINSTALLER_H

View File

@ -1,152 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NativeMessagingBase.h"
#include <QStandardPaths>
#include "config-keepassx.h"
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef Q_OS_LINUX
#include <sys/epoll.h>
#include <unistd.h>
#endif
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#endif
NativeMessagingBase::NativeMessagingBase(const bool enabled)
{
#ifdef Q_OS_WIN
Q_UNUSED(enabled);
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#else
if (enabled) {
m_notifier.reset(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this));
connect(m_notifier.data(), SIGNAL(activated(int)), this, SLOT(newNativeMessage()));
}
#endif
}
void NativeMessagingBase::newNativeMessage()
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
struct kevent ev[1];
struct timespec ts = {5, 0};
int fd = kqueue();
if (fd == -1) {
m_notifier->setEnabled(false);
return;
}
EV_SET(ev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, nullptr);
if (kevent(fd, ev, 1, nullptr, 0, &ts) == -1) {
m_notifier->setEnabled(false);
::close(fd);
return;
}
int ret = kevent(fd, NULL, 0, ev, 1, &ts);
if (ret < 1) {
m_notifier->setEnabled(false);
::close(fd);
return;
}
#elif defined(Q_OS_LINUX)
int fd = epoll_create(5);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = 0;
if (epoll_ctl(fd, EPOLL_CTL_ADD, 0, &event) != 0) {
m_notifier->setEnabled(false);
::close(fd);
return;
}
if (epoll_wait(fd, &event, 1, 5000) < 1) {
m_notifier->setEnabled(false);
::close(fd);
return;
}
#endif
readLength();
#ifndef Q_OS_WIN
::close(fd);
#endif
}
void NativeMessagingBase::readNativeMessages()
{
#ifdef Q_OS_WIN
quint32 length = 0;
while (m_running.load() != 0 && !std::cin.eof()) {
length = 0;
std::cin.readsome(reinterpret_cast<char*>(&length), 4);
readStdIn(length);
QThread::msleep(100);
}
#endif
}
QString NativeMessagingBase::jsonToString(const QJsonObject& json) const
{
return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
}
void NativeMessagingBase::sendReply(const QJsonObject& json)
{
if (!json.isEmpty()) {
sendReply(jsonToString(json));
}
}
void NativeMessagingBase::sendReply(const QString& reply)
{
if (!reply.isEmpty()) {
QByteArray bytes = reply.toUtf8();
uint len = bytes.size();
std::cout << char(((len >> 0) & 0xFF)) << char(((len >> 8) & 0xFF)) << char(((len >> 16) & 0xFF))
<< char(((len >> 24) & 0xFF));
std::cout << reply.toStdString() << std::flush;
}
}
QString NativeMessagingBase::getLocalServerPath() const
{
const QString serverPath = "/kpxc_server";
#if defined(KEEPASSXC_DIST_SNAP)
return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverPath;
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// Use XDG_RUNTIME_DIR instead of /tmp if it's available
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
return path.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverPath
: path + serverPath;
#else // Q_OS_MACOS, Q_OS_WIN and others
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverPath;
#endif
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NATIVEMESSAGINGBASE_H
#define NATIVEMESSAGINGBASE_H
#include <QAtomicInt>
#include <QFuture>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMutex>
#include <QObject>
#include <QSocketNotifier>
#include <QtConcurrent/QtConcurrent>
#include <iostream>
#include <unistd.h>
#ifndef Q_OS_WIN
#include <sys/socket.h>
#include <sys/types.h>
#endif
static const int NATIVE_MSG_MAX_LENGTH = 1024 * 1024;
class NativeMessagingBase : public QObject
{
Q_OBJECT
public:
explicit NativeMessagingBase(const bool enabled);
~NativeMessagingBase() = default;
protected slots:
void newNativeMessage();
protected:
virtual void readLength() = 0;
virtual bool readStdIn(const quint32 length) = 0;
virtual void readNativeMessages();
QString jsonToString(const QJsonObject& json) const;
void sendReply(const QJsonObject& json);
void sendReply(const QString& reply);
QString getLocalServerPath() const;
protected:
QAtomicInt m_running;
QSharedPointer<QSocketNotifier> m_notifier;
QFuture<void> m_future;
};
#endif // NATIVEMESSAGINGBASE_H

View File

@ -1,222 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NativeMessagingHost.h"
#include "BrowserSettings.h"
#include "sodium.h"
#include <QMutexLocker>
#include <QtNetwork>
#include <iostream>
#ifdef Q_OS_WIN
#include <Winsock2.h>
#endif
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent, const bool enabled)
: NativeMessagingBase(enabled)
, m_mutex(QMutex::Recursive)
, m_browserService(parent)
, m_browserClients(m_browserService)
{
m_localServer.reset(new QLocalServer(this));
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
m_running.store(0);
if (browserSettings()->isEnabled() && m_running.load() == 0) {
run();
}
connect(&m_browserService, SIGNAL(databaseLocked()), this, SLOT(databaseLocked()));
connect(&m_browserService, SIGNAL(databaseUnlocked()), this, SLOT(databaseUnlocked()));
}
NativeMessagingHost::~NativeMessagingHost()
{
stop();
}
int NativeMessagingHost::init()
{
QMutexLocker locker(&m_mutex);
return sodium_init();
}
void NativeMessagingHost::run()
{
QMutexLocker locker(&m_mutex);
if (m_running.load() == 0 && init() == -1) {
return;
}
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
if (browserSettings()->updateBinaryPath()) {
browserSettings()->updateBinaryPaths(
browserSettings()->useCustomProxy() ? browserSettings()->customProxyLocation() : "");
}
m_running.store(1);
#ifdef Q_OS_WIN
m_future =
QtConcurrent::run(this, static_cast<void (NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
#endif
if (browserSettings()->supportBrowserProxy()) {
QString serverPath = getLocalServerPath();
QFile::remove(serverPath);
// Ensure that STDIN is not being listened when proxy is used
if (m_notifier && m_notifier->isEnabled()) {
m_notifier->setEnabled(false);
}
if (m_localServer->isListening()) {
m_localServer->close();
}
m_localServer->listen(serverPath);
connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(newLocalConnection()));
} else {
m_localServer->close();
}
}
void NativeMessagingHost::stop()
{
databaseLocked();
QMutexLocker locker(&m_mutex);
m_socketList.clear();
m_running.testAndSetOrdered(1, 0);
m_future.waitForFinished();
m_localServer->close();
}
void NativeMessagingHost::readLength()
{
quint32 length = 0;
std::cin.read(reinterpret_cast<char*>(&length), 4);
if (!std::cin.eof() && length > 0) {
readStdIn(length);
} else {
m_notifier->setEnabled(false);
}
}
bool NativeMessagingHost::readStdIn(const quint32 length)
{
if (length <= 0) {
return false;
}
QByteArray arr;
arr.reserve(length);
QMutexLocker locker(&m_mutex);
for (quint32 i = 0; i < length; ++i) {
int c = std::getchar();
if (c == EOF) {
// message ended prematurely, ignore it and return
return false;
}
arr.append(static_cast<char>(c));
}
if (arr.length() > 0) {
sendReply(m_browserClients.readResponse(arr));
}
return true;
}
void NativeMessagingHost::newLocalConnection()
{
QLocalSocket* socket = m_localServer->nextPendingConnection();
if (socket) {
connect(socket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
connect(socket, SIGNAL(disconnected()), this, SLOT(disconnectSocket()));
}
}
void NativeMessagingHost::newLocalMessage()
{
QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
if (!socket || socket->bytesAvailable() <= 0) {
return;
}
socket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
int socketDesc = socket->socketDescriptor();
if (socketDesc) {
int max = NATIVE_MSG_MAX_LENGTH;
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
}
QByteArray arr = socket->readAll();
if (arr.isEmpty()) {
return;
}
QMutexLocker locker(&m_mutex);
if (!m_socketList.contains(socket)) {
m_socketList.push_back(socket);
}
QString reply = jsonToString(m_browserClients.readResponse(arr));
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
QByteArray arr = reply.toUtf8();
socket->write(arr.constData(), arr.length());
socket->flush();
}
}
void NativeMessagingHost::sendReplyToAllClients(const QJsonObject& json)
{
QString reply = jsonToString(json);
QMutexLocker locker(&m_mutex);
for (const auto socket : m_socketList) {
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
QByteArray arr = reply.toUtf8();
socket->write(arr.constData(), arr.length());
socket->flush();
}
}
}
void NativeMessagingHost::disconnectSocket()
{
QLocalSocket* socket(qobject_cast<QLocalSocket*>(QObject::sender()));
QMutexLocker locker(&m_mutex);
for (auto s : m_socketList) {
if (s == socket) {
m_socketList.removeOne(s);
}
}
}
void NativeMessagingHost::databaseLocked()
{
QJsonObject response;
response["action"] = QString("database-locked");
sendReplyToAllClients(response);
}
void NativeMessagingHost::databaseUnlocked()
{
QJsonObject response;
response["action"] = QString("database-unlocked");
sendReplyToAllClients(response);
}

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NATIVEMESSAGINGHOST_H
#define NATIVEMESSAGINGHOST_H
#include "BrowserClients.h"
#include "BrowserService.h"
#include "NativeMessagingBase.h"
#include "gui/DatabaseTabWidget.h"
class NativeMessagingHost : public NativeMessagingBase
{
Q_OBJECT
typedef QList<QLocalSocket*> SocketList;
public:
explicit NativeMessagingHost(DatabaseTabWidget* parent = nullptr, const bool enabled = false);
~NativeMessagingHost() override;
int init();
void run();
void stop();
signals:
void quit();
private:
void readLength() override;
bool readStdIn(const quint32 length) override;
void sendReplyToAllClients(const QJsonObject& json);
private slots:
void databaseLocked();
void databaseUnlocked();
void newLocalConnection();
void newLocalMessage();
void disconnectSocket();
private:
QMutex m_mutex;
BrowserService m_browserService;
BrowserClients m_browserClients;
QSharedPointer<QLocalServer> m_localServer;
SocketList m_socketList;
};
#endif // NATIVEMESSAGINGHOST_H

View File

@ -44,6 +44,9 @@
#ifdef Q_OS_MACOS
#include "gui/osutils/macutils/MacUtils.h"
#ifdef WITH_XC_TOUCHID
#include "touchid/TouchID.h"
#endif
#endif
#ifdef WITH_XC_UPDATECHECK
@ -56,7 +59,7 @@
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
#if defined(WITH_XC_KEESHARE)
#ifdef WITH_XC_KEESHARE
#include "keeshare/KeeShare.h"
#include "keeshare/SettingsPageKeeShare.h"
#endif
@ -66,9 +69,8 @@
#endif
#ifdef WITH_XC_BROWSER
#include "browser/BrowserOptionDialog.h"
#include "browser/BrowserSettings.h"
#include "browser/NativeMessagingHost.h"
#include "browser/BrowserService.h"
#include "browser/BrowserSettingsPage.h"
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
@ -77,61 +79,6 @@
#include <QtDBus/QtDBus>
#endif
#include "gui/ApplicationSettingsWidget.h"
#include "gui/PasswordGeneratorWidget.h"
#include "touchid/TouchID.h"
#ifdef WITH_XC_BROWSER
class BrowserPlugin : public ISettingsPage
{
public:
explicit BrowserPlugin(DatabaseTabWidget* tabWidget)
{
m_nativeMessagingHost =
QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, browserSettings()->isEnabled()));
}
~BrowserPlugin()
{
}
QString name() override
{
return QObject::tr("Browser Integration");
}
QIcon icon() override
{
return Resources::instance()->icon("internet-web-browser");
}
QWidget* createWidget() override
{
BrowserOptionDialog* dlg = new BrowserOptionDialog();
return dlg;
}
void loadSettings(QWidget* widget) override
{
qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
}
void saveSettings(QWidget* widget) override
{
qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
if (browserSettings()->isEnabled()) {
m_nativeMessagingHost->run();
} else {
m_nativeMessagingHost->stop();
}
}
private:
QSharedPointer<NativeMessagingHost> m_nativeMessagingHost;
};
#endif
const QString MainWindow::BaseWindowTitle = "KeePassXC";
MainWindow* g_MainWindow = nullptr;
@ -186,13 +133,19 @@ MainWindow::MainWindow()
restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray());
restoreState(config()->get(Config::GUI_MainWindowState).toByteArray());
#ifdef WITH_XC_BROWSER
m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, browserService(), &BrowserService::databaseLocked);
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, browserService(), &BrowserService::databaseUnlocked);
connect(m_ui->tabWidget,
&DatabaseTabWidget::activateDatabaseChanged,
browserService(),
&BrowserService::activeDatabaseChanged);
#endif
#ifdef WITH_XC_SSHAGENT
connect(sshAgent(), SIGNAL(error(QString)), this, SLOT(showErrorMessage(QString)));
connect(sshAgent(), SIGNAL(enabledChanged(bool)), this, SLOT(agentEnabled(bool)));
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage());
m_entryContextMenu->addSeparator();
m_entryContextMenu->addAction(m_ui->actionEntryAddToAgent);
@ -565,6 +518,15 @@ MainWindow::~MainWindow()
{
}
QList<DatabaseWidget*> MainWindow::getOpenDatabases()
{
QList<DatabaseWidget*> dbWidgets;
for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
dbWidgets << m_ui->tabWidget->databaseWidgetFromIndex(i);
}
return dbWidgets;
}
void MainWindow::showErrorMessage(const QString& message)
{
m_ui->globalMessageWidget->showMessage(message, MessageWidget::Error);

View File

@ -48,6 +48,8 @@ public:
MainWindow();
~MainWindow();
QList<DatabaseWidget*> getOpenDatabases();
enum StackedWidgetIndex
{
DatabaseTabScreen = 0,

View File

@ -34,7 +34,6 @@ DatabaseSettingsWidgetBrowser::DatabaseSettingsWidgetBrowser(QWidget* parent)
, m_ui(new Ui::DatabaseSettingsWidgetBrowser())
, m_customData(new CustomData(this))
, m_customDataModel(new QStandardItemModel(this))
, m_browserService(nullptr)
{
m_ui->setupUi(this);
m_ui->removeCustomDataButton->setEnabled(false);
@ -254,7 +253,7 @@ void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData()
return;
}
m_browserService.convertAttributesToCustomData(m_db);
BrowserService::convertAttributesToCustomData(m_db);
}
void DatabaseSettingsWidgetBrowser::refreshDatabaseID()

View File

@ -77,7 +77,6 @@ protected:
private:
QPointer<CustomData> m_customData;
QPointer<QStandardItemModel> m_customDataModel;
BrowserService m_browserService;
};
#endif // KEEPASSXC_DATABASESETTINGSWIDGETBROWSER_H

View File

@ -1,5 +1,4 @@
# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2020 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
@ -15,19 +14,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if(WITH_XC_BROWSER)
include_directories(${BROWSER_SOURCE_DIR})
set(proxy_SOURCES
../core/Alloc.cpp
../browser/BrowserShared.cpp
keepassxc-proxy.cpp
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
NativeMessagingHost.cpp)
NativeMessagingProxy.cpp)
add_library(proxy STATIC ${proxy_SOURCES})
target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
target_link_libraries(keepassxc-proxy proxy)
# Alloc must be defined in a static library to prevent clashing with clang ASAN definitions
add_library(proxy_alloc STATIC ../core/Alloc.cpp)
target_link_libraries(proxy_alloc PRIVATE Qt5::Core ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy ${proxy_SOURCES})
target_link_libraries(keepassxc-proxy proxy_alloc Qt5::Core Qt5::Network)
install(TARGETS keepassxc-proxy
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION ${PROXY_INSTALL_DIR} COMPONENT Runtime)
@ -56,7 +53,4 @@ if(WITH_XC_BROWSER)
COMMAND ${CMAKE_COMMAND} -E copy keepassxc-proxy ${PROXY_APP_DIR}/keepassxc-proxy
COMMENT "Copying keepassxc-proxy inside the application")
endif()
if(MINGW)
target_link_libraries(keepassxc-proxy Wtsapi32.lib Ws2_32.lib)
endif()
endif()

View File

@ -1,133 +0,0 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NativeMessagingHost.h"
#include <QCoreApplication>
#ifdef Q_OS_WIN
#include <winsock2.h>
#endif
NativeMessagingHost::NativeMessagingHost()
: NativeMessagingBase(true)
{
m_localSocket = new QLocalSocket();
m_localSocket->connectToServer(getLocalServerPath());
m_localSocket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
int socketDesc = m_localSocket->socketDescriptor();
if (socketDesc) {
int max = NATIVE_MSG_MAX_LENGTH;
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
}
#ifdef Q_OS_WIN
m_running.store(1);
m_future = QtConcurrent::run(this, &NativeMessagingHost::readNativeMessages);
#endif
connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
connect(m_localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket()));
connect(m_localSocket,
SIGNAL(stateChanged(QLocalSocket::LocalSocketState)),
SLOT(socketStateChanged(QLocalSocket::LocalSocketState)));
}
NativeMessagingHost::~NativeMessagingHost()
{
#ifdef Q_OS_WIN
m_future.waitForFinished();
#endif
}
void NativeMessagingHost::readNativeMessages()
{
#ifdef Q_OS_WIN
quint32 length = 0;
while (m_running.load() == 1 && !std::cin.eof()) {
length = 0;
std::cin.read(reinterpret_cast<char*>(&length), 4);
if (!readStdIn(length)) {
QCoreApplication::quit();
}
QThread::msleep(1);
}
#endif
}
void NativeMessagingHost::readLength()
{
quint32 length = 0;
std::cin.read(reinterpret_cast<char*>(&length), 4);
if (!std::cin.eof() && length > 0) {
readStdIn(length);
} else {
QCoreApplication::quit();
}
}
bool NativeMessagingHost::readStdIn(const quint32 length)
{
if (length <= 0) {
return false;
}
QByteArray arr;
arr.reserve(length);
for (quint32 i = 0; i < length; ++i) {
int c = std::getchar();
if (c == EOF) {
// message ended prematurely, ignore it and return
return false;
}
arr.append(static_cast<char>(c));
}
if (arr.length() > 0 && m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
m_localSocket->write(arr.constData(), arr.length());
m_localSocket->flush();
}
return true;
}
void NativeMessagingHost::newLocalMessage()
{
if (!m_localSocket || m_localSocket->bytesAvailable() <= 0) {
return;
}
QByteArray arr = m_localSocket->readAll();
if (!arr.isEmpty()) {
sendReply(arr);
}
}
void NativeMessagingHost::deleteSocket()
{
if (m_notifier) {
m_notifier->setEnabled(false);
}
m_localSocket->deleteLater();
QCoreApplication::quit();
}
void NativeMessagingHost::socketStateChanged(QLocalSocket::LocalSocketState socketState)
{
if (socketState == QLocalSocket::UnconnectedState || socketState == QLocalSocket::ClosingState) {
m_running.testAndSetOrdered(1, 0);
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2020 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 "NativeMessagingProxy.h"
#include "browser/BrowserShared.h"
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrent>
#include <iostream>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <windows.h>
#endif
NativeMessagingProxy::NativeMessagingProxy()
: QObject()
{
connect(this,
&NativeMessagingProxy::stdinMessage,
this,
&NativeMessagingProxy::transferStdinMessage,
Qt::QueuedConnection);
setupStandardInput();
setupLocalSocket();
}
void NativeMessagingProxy::setupStandardInput()
{
#ifdef Q_OS_WIN
setmode(fileno(stdin), _O_BINARY);
setmode(fileno(stdout), _O_BINARY);
#endif
QtConcurrent::run([this] {
while (std::cin.good()) {
if (std::cin.peek() != EOF) {
uint length = 0;
for (uint i = 0; i < sizeof(uint); ++i) {
length |= getchar() << (i * 8);
}
QString msg;
msg.reserve(length);
for (uint i = 0; i < length; ++i) {
msg.append(getchar());
}
if (msg.length() > 0) {
emit stdinMessage(msg);
}
}
QThread::msleep(100);
}
QCoreApplication::quit();
});
}
void NativeMessagingProxy::transferStdinMessage(const QString& msg)
{
if (m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
m_localSocket->write(msg.toUtf8(), msg.length());
m_localSocket->flush();
}
}
void NativeMessagingProxy::setupLocalSocket()
{
m_localSocket.reset(new QLocalSocket());
m_localSocket->connectToServer(BrowserShared::localServerPath());
m_localSocket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
connect(m_localSocket.data(), SIGNAL(readyRead()), this, SLOT(transferSocketMessage()));
connect(m_localSocket.data(), SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
}
void NativeMessagingProxy::transferSocketMessage()
{
auto msg = m_localSocket->readAll();
if (!msg.isEmpty()) {
// Explicitly write the message length as 1 byte chunks
uint len = msg.size();
std::cout.write(reinterpret_cast<char*>(&len), sizeof(len));
// Write the message and flush the stream
std::cout << msg.toStdString() << std::flush;
}
}
void NativeMessagingProxy::socketDisconnected()
{
// Shutdown the proxy when disconnected from the application
QCoreApplication::quit();
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2020 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 NATIVEMESSAGINGPROXY_H
#define NATIVEMESSAGINGPROXY_H
#include <QLocalSocket>
#include <QObject>
#include <QScopedPointer>
class QWinEventNotifier;
class QSocketNotifier;
class NativeMessagingProxy : public QObject
{
Q_OBJECT
public:
NativeMessagingProxy();
~NativeMessagingProxy() override = default;
signals:
void stdinMessage(QString msg);
public slots:
void transferSocketMessage();
void transferStdinMessage(const QString& msg);
void socketDisconnected();
private:
void setupStandardInput();
void setupLocalSocket();
private:
QScopedPointer<QLocalSocket> m_localSocket;
Q_DISABLE_COPY(NativeMessagingProxy)
};
#endif // NATIVEMESSAGINGPROXY_H

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NativeMessagingHost.h"
#include "NativeMessagingProxy.h"
#include <QCoreApplication>
#include <iostream>
#ifndef Q_OS_WIN
@ -79,6 +80,6 @@ int main(int argc, char* argv[])
#else
SetConsoleCtrlHandler(static_cast<PHANDLER_ROUTINE>(ConsoleHandler), TRUE);
#endif
NativeMessagingHost host;
NativeMessagingProxy proxy;
return a.exec();
}

View File

@ -20,15 +20,6 @@
#include "AgentSettingsWidget.h"
#include "core/Resources.h"
AgentSettingsPage::AgentSettingsPage(DatabaseTabWidget* tabWidget)
{
Q_UNUSED(tabWidget);
}
AgentSettingsPage::~AgentSettingsPage()
{
}
QString AgentSettingsPage::name()
{
return QObject::tr("SSH Agent");

View File

@ -25,8 +25,8 @@
class AgentSettingsPage : public ISettingsPage
{
public:
AgentSettingsPage(DatabaseTabWidget* tabWidget);
~AgentSettingsPage() override;
AgentSettingsPage() = default;
~AgentSettingsPage() override = default;
QString name() override;
QIcon icon() override;

View File

@ -16,11 +16,13 @@
*/
#include "TestBrowser.h"
#include "TestGlobal.h"
#include "browser/BrowserSettings.h"
#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "sodium/crypto_box.h"
#include <QString>
QTEST_GUILESS_MAIN(TestBrowser)
@ -35,12 +37,12 @@ const QString CLIENTID = "testClient";
void TestBrowser::initTestCase()
{
QVERIFY(Crypto::init());
m_browserService.reset(new BrowserService(nullptr));
m_browserAction.reset(new BrowserAction(*m_browserService.data()));
m_browserService = browserService();
}
void TestBrowser::cleanupTestCase()
void TestBrowser::init()
{
m_browserAction.reset(new BrowserAction());
}
/**
@ -54,7 +56,7 @@ void TestBrowser::testChangePublicKeys()
json["publicKey"] = PUBLICKEY;
json["nonce"] = NONCE;
auto response = m_browserAction->handleAction(json);
auto response = m_browserAction->processClientMessage(json);
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
QCOMPARE(response["success"].toString(), TRUE_STR);
@ -393,62 +395,6 @@ void TestBrowser::testSortEntries()
QCOMPARE(result[3]->url(), QString("github.com/login"));
}
void TestBrowser::testGetDatabaseGroups()
{
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QScopedPointer<Group> group1(new Group());
group1->setParent(root);
group1->setName("group1");
QScopedPointer<Group> group2(new Group());
group2->setParent(root);
group2->setName("group2");
QScopedPointer<Group> group3(new Group());
group3->setParent(root);
group3->setName("group3");
QScopedPointer<Group> group2_1(new Group());
group2_1->setParent(group2.data());
group2_1->setName("group2_1");
QScopedPointer<Group> group2_2(new Group());
group2_2->setParent(group2.data());
group2_2->setName("group2_2");
QScopedPointer<Group> group2_1_1(new Group());
group2_1_1->setParent(group2_1.data());
group2_1_1->setName("group2_1_1");
auto result = m_browserService->getDatabaseGroups(db);
QCOMPARE(result.length(), 1);
auto groups = result["groups"].toArray();
auto first = groups.at(0);
auto children = first.toObject()["children"].toArray();
QCOMPARE(first.toObject()["name"].toString(), QString("Root"));
QCOMPARE(children.size(), 3);
auto firstChild = children.at(0);
auto secondChild = children.at(1);
auto thirdChild = children.at(2);
QCOMPARE(firstChild.toObject()["name"].toString(), QString("group1"));
QCOMPARE(secondChild.toObject()["name"].toString(), QString("group2"));
QCOMPARE(thirdChild.toObject()["name"].toString(), QString("group3"));
auto childrenOfSecond = secondChild.toObject()["children"].toArray();
auto firstOfCOS = childrenOfSecond.at(0);
auto secondOfCOS = childrenOfSecond.at(1);
QCOMPARE(firstOfCOS.toObject()["name"].toString(), QString("group2_1"));
QCOMPARE(secondOfCOS.toObject()["name"].toString(), QString("group2_2"));
auto lastChildren = firstOfCOS.toObject()["children"].toArray();
auto lastChild = lastChildren.at(0);
QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1"));
}
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
{
QList<Entry*> entries;

View File

@ -30,7 +30,7 @@ class TestBrowser : public QObject
private slots:
void initTestCase();
void cleanupTestCase();
void init();
void testChangePublicKeys();
void testEncryptMessage();
@ -46,14 +46,13 @@ private slots:
void testInvalidEntries();
void testSubdomainsAndPaths();
void testSortEntries();
void testGetDatabaseGroups();
void testValidURLs();
private:
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
QScopedPointer<BrowserAction> m_browserAction;
QScopedPointer<BrowserService> m_browserService;
QPointer<BrowserService> m_browserService;
};
#endif // KEEPASSXC_TESTBROWSER_H

Binary file not shown.

View File

@ -31,6 +31,7 @@
#include <QTableView>
#include <QToolBar>
#include "browser/BrowserService.h"
#include "config-keepassx-tests.h"
#include "core/Bootstrap.h"
#include "core/Config.h"
@ -82,30 +83,19 @@ void TestGuiBrowser::initTestCase()
Bootstrap::restoreMainWindowState(*m_mainWindow);
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
m_mainWindow->show();
// Load the NewDatabase.kdbx file into temporary storage
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabaseBrowser.kdbx"));
QVERIFY(sourceDbFile.open(QIODevice::ReadOnly));
QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
sourceDbFile.close();
}
// Every test starts with opening the temp database
void TestGuiBrowser::init()
{
m_dbFile.reset(new TemporaryFile());
// Write the temp storage to a temp database file for use in our tests
QVERIFY(m_dbFile->open());
QCOMPARE(m_dbFile->write(m_dbData), static_cast<qint64>((m_dbData.size())));
m_dbFileName = QFileInfo(m_dbFile->fileName()).fileName();
m_dbFilePath = m_dbFile->fileName();
m_dbFile->close();
m_dbFile->copyFromFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabaseBrowser.kdbx"));
// make sure window is activated or focus tests may fail
m_mainWindow->activateWindow();
QApplication::processEvents();
fileDialog()->setNextFileName(m_dbFilePath);
fileDialog()->setNextFileName(m_dbFile->fileName());
triggerAction("actionDatabaseOpen");
auto* databaseOpenWidget = m_tabWidget->currentDatabaseWidget()->findChild<QWidget*>("databaseOpenWidget");
@ -241,6 +231,28 @@ void TestGuiBrowser::testAdditionalURLs()
}
}
void TestGuiBrowser::testGetDatabaseGroups()
{
auto result = browserService()->getDatabaseGroups();
QCOMPARE(result.length(), 1);
auto groups = result["groups"].toArray();
auto first = groups.at(0);
auto children = first.toObject()["children"].toArray();
QCOMPARE(first.toObject()["name"].toString(), QString("NewDatabase"));
QCOMPARE(children.size(), 6);
auto firstChild = children.at(0).toObject();
auto secondChild = children.at(1).toObject();
QCOMPARE(firstChild["name"].toString(), QString("General"));
QCOMPARE(secondChild["name"].toString(), QString("Windows"));
auto subGroups = firstChild["children"].toArray();
QCOMPARE(subGroups.count(), 1);
auto subGroupObj = subGroups.at(0).toObject();
QCOMPARE(subGroupObj["name"].toString(), QString("SubGroup"));
}
void TestGuiBrowser::triggerAction(const QString& name)
{
auto* action = m_mainWindow->findChild<QAction*>(name);

View File

@ -45,6 +45,7 @@ private slots:
void testEntrySettings();
void testAdditionalURLs();
void testGetDatabaseGroups();
private:
void triggerAction(const QString& name);
@ -57,10 +58,7 @@ private:
QPointer<DatabaseTabWidget> m_tabWidget;
QPointer<DatabaseWidget> m_dbWidget;
QSharedPointer<Database> m_db;
QByteArray m_dbData;
QScopedPointer<TemporaryFile> m_dbFile;
QString m_dbFileName;
QString m_dbFilePath;
};
#endif // KEEPASSXC_TESTGUIBROWSER_H