diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e57973ae8..c5fa3a1ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index fec5b985a..361dc2a9c 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -1,6 +1,5 @@ /* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 #include @@ -27,14 +28,31 @@ #include #include -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 sk(sa.cbegin(), sa.cend()); std::vector 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 sk(sa.cbegin(), sa.cend()); std::vector d; - d.resize(NATIVE_MSG_MAX_LENGTH); + d.resize(BrowserShared::NATIVEMSG_MAX_LENGTH); if (m.empty() || n.empty() || ck.empty() || sk.empty()) { return QByteArray(); diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index a8af0915e..c65409dd8 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 -#include -#include -#include +#include -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; }; diff --git a/src/browser/BrowserClients.cpp b/src/browser/BrowserClients.cpp deleted file mode 100644 index 083df3945..000000000 --- a/src/browser/BrowserClients.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "BrowserClients.h" -#include -#include - -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 ba = QSharedPointer::create(m_browserService); - ClientPtr client = ClientPtr::create(clientID, ba); - m_clients.push_back(client); - return m_clients.back(); -} diff --git a/src/browser/BrowserClients.h b/src/browser/BrowserClients.h deleted file mode 100644 index 1fa3dfe17..000000000 --- a/src/browser/BrowserClients.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#ifndef BROWSERCLIENTS_H -#define BROWSERCLIENTS_H - -#include "BrowserAction.h" -#include -#include -#include -#include -#include - -class BrowserClients -{ - struct Client - { - Client(const QString& id, QSharedPointer ba) - : clientID(id) - , browserAction(ba) - { - } - QString clientID; - QSharedPointer browserAction; - }; - - typedef QSharedPointer 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 m_clients; - BrowserService& m_browserService; -}; - -#endif // BROWSERCLIENTS_H diff --git a/src/browser/BrowserHost.cpp b/src/browser/BrowserHost.cpp new file mode 100644 index 000000000..62c3e9cd8 --- /dev/null +++ b/src/browser/BrowserHost.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#include "BrowserHost.h" +#include "BrowserSettings.h" +#include "BrowserShared.h" + +#include +#include +#include +#include +#include + +#include "sodium.h" +#include + +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(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(QObject::sender()); + m_socketList.removeOne(socket); +} diff --git a/src/proxy/NativeMessagingHost.h b/src/browser/BrowserHost.h similarity index 56% rename from src/proxy/NativeMessagingHost.h rename to src/browser/BrowserHost.h index 5bedd9de5..ea8e07409 100644 --- a/src/proxy/NativeMessagingHost.h +++ b/src/browser/BrowserHost.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 +#include +#include -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 m_localServer; + QList m_socketList; }; #endif // NATIVEMESSAGINGHOST_H diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 5aa5e77ed..b83af627a 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2013 Francois Ferrand * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 #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& selectedDb) +QJsonObject BrowserService::getDatabaseGroups() { - auto db = selectedDb ? selectedDb : getDatabase(); + auto db = getDatabase(); if (!db) { return {}; } @@ -208,15 +216,6 @@ QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer& 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& 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, 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, - const QString& uuid, - const QString& login, - const QString& password, - const QString& url, - const QString& submitUrl) +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 BrowserService::searchEntries(const QString& url, const QString& s // Get the list of databases to search QList> databases; if (browserSettings()->searchInAllDatabases()) { - const int count = m_dbTabWidget->count(); - for (int i = 0; i < count; ++i) { - if (auto* dbWidget = qobject_cast(m_dbTabWidget->widget(i))) { - if (const auto& db = dbWidget->database()) { - if (databaseConnected(db)) { - databases << 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 BrowserService::searchEntries(const QString& url, const QString& s return entries; } -void BrowserService::convertAttributesToCustomData(const QSharedPointer& currentDb) +void BrowserService::convertAttributesToCustomData(QSharedPointer db) { - auto db = currentDb ? currentDb : getDatabase(); if (!db) { return; } @@ -806,7 +757,7 @@ QList BrowserService::confirmEntries(QList& 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 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 BrowserService::getDatabase() QSharedPointer BrowserService::selectedDatabase() { QList 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 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& db) const +int BrowserService::moveKeysToCustomData(Entry* entry, QSharedPointer db) { int keyCounter = 0; for (const auto& key : entry->attributes()->keys()) { @@ -1179,14 +1123,9 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer 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::create()); + } + + auto& action = m_browserClients.value(clientID); + auto response = action->processClientMessage(message); + m_browserHost->sendClientMessage(response); +} diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index 3157df61f..6de5e49bf 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2013 Francois Ferrand * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 +#include +#include #include typedef QPair 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& 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& selectedDb = {}); - QList searchEntries(const QSharedPointer& db, const QString& url, const QString& submitUrl); - QList searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList); - void convertAttributesToCustomData(const QSharedPointer& 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 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 searchEntries(const QSharedPointer& db, const QString& url, const QString& submitUrl); + QList searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList); QList sortEntries(QList& pwEntries, const QString& host, const QString& submitUrl); QList confirmEntries(QList& 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& selectedDb = {}); int @@ -135,21 +138,35 @@ private: QString baseDomain(const QString& hostname) const; QSharedPointer getDatabase(); QSharedPointer selectedDatabase(); - QJsonArray getChildrenFromGroup(Group* group); - bool moveSettingsToCustomData(Entry* entry, const QString& name) const; - int moveKeysToCustomData(Entry* entry, const QSharedPointer& db) const; - bool checkLegacySettings(); + QString getDatabaseRootUuid(); + QString getDatabaseRecycleBinUuid(); + + bool checkLegacySettings(QSharedPointer 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 db); + + QPointer m_browserHost; + QHash> m_browserClients; + bool m_dialogActive; bool m_bringToFrontRequested; WindowState m_prevWindowState; QUuid m_keepassBrowserUUID; + QPointer m_currentDatabaseWidget; + + Q_DISABLE_COPY(BrowserService); + friend class TestBrowser; }; +static inline BrowserService* browserService() +{ + return BrowserService::instance(); +} + #endif // BROWSERSERVICE_H diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index 7fa80ea3b..5d6514cae 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -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(); } diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index 9340cd0a3..3f5cceea7 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -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() diff --git a/src/browser/BrowserSettingsPage.cpp b/src/browser/BrowserSettingsPage.cpp new file mode 100644 index 000000000..692854bf8 --- /dev/null +++ b/src/browser/BrowserSettingsPage.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#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(widget)->loadSettings(); +} + +void BrowserSettingsPage::saveSettings(QWidget* widget) +{ + qobject_cast(widget)->saveSettings(); + browserService()->setEnabled(browserSettings()->isEnabled()); +} diff --git a/src/browser/BrowserSettingsPage.h b/src/browser/BrowserSettingsPage.h new file mode 100644 index 000000000..9e669b194 --- /dev/null +++ b/src/browser/BrowserSettingsPage.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#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 diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserSettingsWidget.cpp similarity index 71% rename from src/browser/BrowserOptionDialog.cpp rename to src/browser/BrowserSettingsWidget.cpp index 8a67d62da..15b20b19a 100644 --- a/src/browser/BrowserOptionDialog.cpp +++ b/src/browser/BrowserSettingsWidget.cpp @@ -1,7 +1,5 @@ /* - * Copyright (C) 2013 Francois Ferrand - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 . */ -#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 -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("Warning: 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("Warning, the keepassxc-proxy application was not found!" - "
Please check the KeePassXC installation directory or confirm the custom path in advanced options." - "
Browser integration WILL NOT WORK without the proxy application." - "
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("Error: The custom proxy location cannot be found!" + "
Browser integration WILL NOT WORK without the proxy application."), + MessageWidget::Error); } else { - m_ui->scriptWarningWidget->setVisible(false); + m_ui->warningWidget->showMessage(tr("Warning: 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(); } diff --git a/src/browser/BrowserOptionDialog.h b/src/browser/BrowserSettingsWidget.h similarity index 65% rename from src/browser/BrowserOptionDialog.h rename to src/browser/BrowserSettingsWidget.h index 5efb808e5..8f9dea62f 100644 --- a/src/browser/BrowserOptionDialog.h +++ b/src/browser/BrowserSettingsWidget.h @@ -1,7 +1,5 @@ /* - * Copyright (C) 2013 Francois Ferrand - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 KeePassXC Team * * 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 . */ -#ifndef BROWSEROPTIONDIALOG_H -#define BROWSEROPTIONDIALOG_H +#ifndef BROWSERSETTINGSWIDGET_H +#define BROWSERSETTINGSWIDGET_H #include #include @@ -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 m_ui; + QScopedPointer m_ui; }; -#endif // BROWSEROPTIONDIALOG_H +#endif // BROWSERSETTINGSWIDGET_H diff --git a/src/browser/BrowserOptionDialog.ui b/src/browser/BrowserSettingsWidget.ui old mode 100755 new mode 100644 similarity index 94% rename from src/browser/BrowserOptionDialog.ui rename to src/browser/BrowserSettingsWidget.ui index 9dabde948..b3458fdcd --- a/src/browser/BrowserOptionDialog.ui +++ b/src/browser/BrowserSettingsWidget.ui @@ -1,7 +1,7 @@ - BrowserOptionDialog - + BrowserSettingsWidget + 0 @@ -49,16 +49,6 @@ General - - - - - 0 - 0 - - - - @@ -351,16 +341,6 @@ - - - - Support a proxy application between KeePassXC and browser extension. - - - Use a proxy application between KeePassXC and browser extension - - - diff --git a/src/browser/BrowserShared.cpp b/src/browser/BrowserShared.cpp new file mode 100644 index 000000000..654201705 --- /dev/null +++ b/src/browser/BrowserShared.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#include "BrowserShared.h" +#include "config-keepassx.h" + +#include +#include +#include +#include + +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 diff --git a/src/browser/BrowserShared.h b/src/browser/BrowserShared.h new file mode 100644 index 000000000..02bee9c44 --- /dev/null +++ b/src/browser/BrowserShared.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_BROWSERSHARED_H +#define KEEPASSXC_BROWSERSHARED_H + +#include + +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 diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt index 7e813eb5b..bb92511bc 100755 --- a/src/browser/CMakeLists.txt +++ b/src/browser/CMakeLists.txt @@ -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}) diff --git a/src/browser/HostInstaller.cpp b/src/browser/HostInstaller.cpp deleted file mode 100644 index f4ffae3b7..000000000 --- a/src/browser/HostInstaller.cpp +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "HostInstaller.h" -#include "config-keepassx.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -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(i))) { - installBrowser(static_cast(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; -} diff --git a/src/browser/HostInstaller.h b/src/browser/HostInstaller.h deleted file mode 100644 index 2136d1c34..000000000 --- a/src/browser/HostInstaller.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#ifndef HOSTINSTALLER_H -#define HOSTINSTALLER_H - -#include -#include -#include - -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 diff --git a/src/browser/NativeMessageInstaller.cpp b/src/browser/NativeMessageInstaller.cpp new file mode 100644 index 000000000..d3b3daf32 --- /dev/null +++ b/src/browser/NativeMessageInstaller.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2017 Sami Vänttinen + * Copyright (C) 2017 KeePassXC Team + * + * 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 . + */ + +#include "NativeMessageInstaller.h" +#include "BrowserSettings.h" +#include "config-keepassx.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(i))) { + setBrowserEnabled(static_cast(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; +} diff --git a/src/browser/NativeMessageInstaller.h b/src/browser/NativeMessageInstaller.h new file mode 100644 index 000000000..4c0e339ee --- /dev/null +++ b/src/browser/NativeMessageInstaller.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Sami Vänttinen + * Copyright (C) 2017 KeePassXC Team + * + * 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 . + */ + +#ifndef NATIVEMESSAGEINSTALLER_H +#define NATIVEMESSAGEINSTALLER_H + +#include "BrowserShared.h" +#include + +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 diff --git a/src/browser/NativeMessagingBase.cpp b/src/browser/NativeMessagingBase.cpp deleted file mode 100644 index 208d28a1e..000000000 --- a/src/browser/NativeMessagingBase.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "NativeMessagingBase.h" -#include - -#include "config-keepassx.h" - -#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) -#include -#include -#include -#include -#endif - -#ifdef Q_OS_LINUX -#include -#include -#endif - -#ifdef Q_OS_WIN -#include -#include -#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(&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 -} diff --git a/src/browser/NativeMessagingBase.h b/src/browser/NativeMessagingBase.h deleted file mode 100644 index b68208c68..000000000 --- a/src/browser/NativeMessagingBase.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#ifndef NATIVEMESSAGINGBASE_H -#define NATIVEMESSAGINGBASE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef Q_OS_WIN -#include -#include -#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 m_notifier; - QFuture m_future; -}; - -#endif // NATIVEMESSAGINGBASE_H diff --git a/src/browser/NativeMessagingHost.cpp b/src/browser/NativeMessagingHost.cpp deleted file mode 100644 index a6c321215..000000000 --- a/src/browser/NativeMessagingHost.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "NativeMessagingHost.h" -#include "BrowserSettings.h" -#include "sodium.h" -#include -#include -#include - -#ifdef Q_OS_WIN -#include -#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(&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(&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(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(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(&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(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); -} diff --git a/src/browser/NativeMessagingHost.h b/src/browser/NativeMessagingHost.h deleted file mode 100644 index 9ce1dab60..000000000 --- a/src/browser/NativeMessagingHost.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#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 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 m_localServer; - SocketList m_socketList; -}; - -#endif // NATIVEMESSAGINGHOST_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f20f3b9d1..a94229376 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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 #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(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(widget)->loadSettings(); - } - - void saveSettings(QWidget* widget) override - { - qobject_cast(widget)->saveSettings(); - if (browserSettings()->isEnabled()) { - m_nativeMessagingHost->run(); - } else { - m_nativeMessagingHost->stop(); - } - } - -private: - QSharedPointer 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 MainWindow::getOpenDatabases() +{ + QList 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); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index a038b7c29..81d8212af 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -48,6 +48,8 @@ public: MainWindow(); ~MainWindow(); + QList getOpenDatabases(); + enum StackedWidgetIndex { DatabaseTabScreen = 0, diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp index 31fe34eef..a37f1f742 100644 --- a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp +++ b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp @@ -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() diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.h b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.h index c3cc0b122..51abf7f39 100644 --- a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.h +++ b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.h @@ -77,7 +77,6 @@ protected: private: QPointer m_customData; QPointer m_customDataModel; - BrowserService m_browserService; }; #endif // KEEPASSXC_DATABASESETTINGSWIDGETBROWSER_H diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index 61dfd1b25..b7bec6deb 100755 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -1,5 +1,4 @@ -# Copyright (C) 2017 Sami Vänttinen -# Copyright (C) 2017 KeePassXC Team +# Copyright (C) 2020 KeePassXC Team # # 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 . 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() diff --git a/src/proxy/NativeMessagingHost.cpp b/src/proxy/NativeMessagingHost.cpp deleted file mode 100644 index 44b3ab7ef..000000000 --- a/src/proxy/NativeMessagingHost.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "NativeMessagingHost.h" -#include - -#ifdef Q_OS_WIN -#include -#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(&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(&length), 4); - if (!readStdIn(length)) { - QCoreApplication::quit(); - } - QThread::msleep(1); - } -#endif -} - -void NativeMessagingHost::readLength() -{ - quint32 length = 0; - std::cin.read(reinterpret_cast(&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(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); - } -} diff --git a/src/proxy/NativeMessagingProxy.cpp b/src/proxy/NativeMessagingProxy.cpp new file mode 100644 index 000000000..a7bd1c0f3 --- /dev/null +++ b/src/proxy/NativeMessagingProxy.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#include "NativeMessagingProxy.h" +#include "browser/BrowserShared.h" + +#include +#include + +#include + +#ifdef Q_OS_WIN +#include +#include +#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(&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(); +} diff --git a/src/proxy/NativeMessagingProxy.h b/src/proxy/NativeMessagingProxy.h new file mode 100644 index 000000000..75e6f03ac --- /dev/null +++ b/src/proxy/NativeMessagingProxy.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * 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 . + */ + +#ifndef NATIVEMESSAGINGPROXY_H +#define NATIVEMESSAGINGPROXY_H + +#include +#include +#include + +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 m_localSocket; + + Q_DISABLE_COPY(NativeMessagingProxy) +}; + +#endif // NATIVEMESSAGINGPROXY_H diff --git a/src/proxy/keepassxc-proxy.cpp b/src/proxy/keepassxc-proxy.cpp index ea472b2c3..b2a2b1458 100644 --- a/src/proxy/keepassxc-proxy.cpp +++ b/src/proxy/keepassxc-proxy.cpp @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -#include "NativeMessagingHost.h" +#include "NativeMessagingProxy.h" #include + #include #ifndef Q_OS_WIN @@ -79,6 +80,6 @@ int main(int argc, char* argv[]) #else SetConsoleCtrlHandler(static_cast(ConsoleHandler), TRUE); #endif - NativeMessagingHost host; + NativeMessagingProxy proxy; return a.exec(); } diff --git a/src/sshagent/AgentSettingsPage.cpp b/src/sshagent/AgentSettingsPage.cpp index eb86f3fce..efadfbab8 100644 --- a/src/sshagent/AgentSettingsPage.cpp +++ b/src/sshagent/AgentSettingsPage.cpp @@ -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"); diff --git a/src/sshagent/AgentSettingsPage.h b/src/sshagent/AgentSettingsPage.h index 015dfb9ac..33f29b055 100644 --- a/src/sshagent/AgentSettingsPage.h +++ b/src/sshagent/AgentSettingsPage.h @@ -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; diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp index 5ddb5e898..5b2f61178 100644 --- a/tests/TestBrowser.cpp +++ b/tests/TestBrowser.cpp @@ -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 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::create(); - auto* root = db->rootGroup(); - - QScopedPointer group1(new Group()); - group1->setParent(root); - group1->setName("group1"); - - QScopedPointer group2(new Group()); - group2->setParent(root); - group2->setName("group2"); - - QScopedPointer group3(new Group()); - group3->setParent(root); - group3->setName("group3"); - - QScopedPointer group2_1(new Group()); - group2_1->setParent(group2.data()); - group2_1->setName("group2_1"); - - QScopedPointer group2_2(new Group()); - group2_2->setParent(group2.data()); - group2_2->setName("group2_2"); - - QScopedPointer 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 TestBrowser::createEntries(QStringList& urls, Group* root) const { QList entries; diff --git a/tests/TestBrowser.h b/tests/TestBrowser.h index 69ba69309..00f9d7528 100644 --- a/tests/TestBrowser.h +++ b/tests/TestBrowser.h @@ -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 createEntries(QStringList& urls, Group* root) const; QScopedPointer m_browserAction; - QScopedPointer m_browserService; + QPointer m_browserService; }; #endif // KEEPASSXC_TESTBROWSER_H diff --git a/tests/data/NewDatabaseBrowser.kdbx b/tests/data/NewDatabaseBrowser.kdbx index 97599fccf..fb327943c 100644 Binary files a/tests/data/NewDatabaseBrowser.kdbx and b/tests/data/NewDatabaseBrowser.kdbx differ diff --git a/tests/gui/TestGuiBrowser.cpp b/tests/gui/TestGuiBrowser.cpp index 7e5d89df4..6eac62798 100644 --- a/tests/gui/TestGuiBrowser.cpp +++ b/tests/gui/TestGuiBrowser.cpp @@ -31,6 +31,7 @@ #include #include +#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("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((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("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(name); diff --git a/tests/gui/TestGuiBrowser.h b/tests/gui/TestGuiBrowser.h index 53a9c73c4..818a36952 100644 --- a/tests/gui/TestGuiBrowser.h +++ b/tests/gui/TestGuiBrowser.h @@ -45,6 +45,7 @@ private slots: void testEntrySettings(); void testAdditionalURLs(); + void testGetDatabaseGroups(); private: void triggerAction(const QString& name); @@ -57,10 +58,7 @@ private: QPointer m_tabWidget; QPointer m_dbWidget; QSharedPointer m_db; - QByteArray m_dbData; QScopedPointer m_dbFile; - QString m_dbFileName; - QString m_dbFilePath; }; #endif // KEEPASSXC_TESTGUIBROWSER_H