mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Complete refactor of Browser Integration classes
* Removed option to attach KeePassXC to the browser extension. Users must use the proxy application to communicate with KeePassXC. * Significantly streamlined proxy code. Used same implementation of stdin/stdout interface across all platforms. * Moved browser service entry point to BrowserService class instead of NativeMessagingHost. BrowserService now coordinates the communication to/from clients. * Moved settings page definition out of MainWindow * Decoupled BrowserService from DatabaseTabWidget * Reduced complexity of various functions and cleaned the ABI (public vs private). * Eliminated BrowserClients class, moved functionality into the BrowserService * Renamed HostInstaller to NativeMessageInstaller and renamed NativeMessageHost to BrowserHost. * Recognize XDG_CONFIG_HOME when installing native message file on Linux. Fix #4121 and fix #4123.
This commit is contained in:
parent
3b4057a78c
commit
a145bf9119
@ -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)
|
||||
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -17,9 +16,11 @@
|
||||
*/
|
||||
|
||||
#include "BrowserAction.h"
|
||||
#include "BrowserService.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "NativeMessagingBase.h"
|
||||
#include "BrowserShared.h"
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Global.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
@ -27,14 +28,31 @@
|
||||
#include <sodium/crypto_box.h>
|
||||
#include <sodium/randombytes.h>
|
||||
|
||||
BrowserAction::BrowserAction(BrowserService& browserService)
|
||||
: m_mutex(QMutex::Recursive)
|
||||
, m_browserService(browserService)
|
||||
, m_associated(false)
|
||||
namespace
|
||||
{
|
||||
enum
|
||||
{
|
||||
ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
|
||||
ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
|
||||
ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
|
||||
ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
|
||||
ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
|
||||
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
|
||||
ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
|
||||
ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
|
||||
ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
|
||||
ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
|
||||
ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
|
||||
ERROR_KEEPASS_INCORRECT_ACTION = 12,
|
||||
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
|
||||
ERROR_KEEPASS_NO_URL_PROVIDED = 14,
|
||||
ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
|
||||
ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
|
||||
ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17
|
||||
};
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::readResponse(const QJsonObject& json)
|
||||
QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
|
||||
{
|
||||
if (json.isEmpty()) {
|
||||
return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
|
||||
@ -51,11 +69,10 @@ QJsonObject BrowserAction::readResponse(const QJsonObject& json)
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
|
||||
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !browserService()->isDatabaseOpened()) {
|
||||
if (m_clientPublicKey.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
} else if (!m_browserService.openDatabase(triggerUnlock)) {
|
||||
} else if (!browserService()->openDatabase(triggerUnlock)) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
}
|
||||
@ -98,7 +115,6 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json)
|
||||
|
||||
QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString clientPublicKey = json.value("publicKey").toString();
|
||||
|
||||
@ -130,7 +146,7 @@ QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const
|
||||
|
||||
QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
@ -153,7 +169,7 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const
|
||||
// Update a legacy database hash if found
|
||||
const QJsonArray hashes = decrypted.value("connectedKeys").toArray();
|
||||
if (!hashes.isEmpty()) {
|
||||
const QString legacyHash = getLegacyDatabaseHash();
|
||||
const QString legacyHash = browserService()->getDatabaseHash(true);
|
||||
if (hashes.contains(legacyHash)) {
|
||||
message["oldHash"] = legacyHash;
|
||||
}
|
||||
@ -167,7 +183,7 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const
|
||||
|
||||
QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
@ -181,12 +197,11 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
|
||||
// Check for identification key. If it's not found, ensure backwards compatibility and use the current public
|
||||
// key
|
||||
const QString idKey = decrypted.value("idKey").toString();
|
||||
const QString id = m_browserService.storeKey((idKey.isEmpty() ? key : idKey));
|
||||
const QString id = browserService()->storeKey((idKey.isEmpty() ? key : idKey));
|
||||
if (id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
}
|
||||
@ -205,7 +220,7 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
|
||||
|
||||
QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
@ -220,8 +235,7 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QString key = m_browserService.getKey(id);
|
||||
const QString key = browserService()->getKey(id);
|
||||
if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
@ -238,11 +252,10 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS
|
||||
|
||||
QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
@ -269,7 +282,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
||||
const QString submit = decrypted.value("submitUrl").toString();
|
||||
const QString auth = decrypted.value("httpAuth").toString();
|
||||
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
|
||||
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList, httpAuth);
|
||||
const QJsonArray users = browserService()->findMatchingEntries(id, url, submit, "", keyList, httpAuth);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
|
||||
@ -311,11 +324,10 @@ QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const
|
||||
|
||||
QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
@ -339,11 +351,11 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
|
||||
const QString groupUuid = decrypted.value("groupUuid").toString();
|
||||
const QString realm;
|
||||
|
||||
BrowserService::ReturnValue result = BrowserService::ReturnValue::Success;
|
||||
bool result = true;
|
||||
if (uuid.isEmpty()) {
|
||||
m_browserService.addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
|
||||
browserService()->addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
|
||||
} else {
|
||||
result = m_browserService.updateEntry(id, uuid, login, password, url, submitUrl);
|
||||
result = browserService()->updateEntry(id, uuid, login, password, url, submitUrl);
|
||||
}
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
@ -351,7 +363,7 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["count"] = QJsonValue::Null;
|
||||
message["entries"] = QJsonValue::Null;
|
||||
message["error"] = getReturnValue(result);
|
||||
message["error"] = result ? QStringLiteral("success") : QStringLiteral("error");
|
||||
message["hash"] = hash;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
@ -359,7 +371,7 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
|
||||
|
||||
QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
@ -374,8 +386,7 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.lockDatabase();
|
||||
browserService()->lockDatabase();
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
@ -388,11 +399,10 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
|
||||
|
||||
QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
@ -407,7 +417,7 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
const QJsonObject groups = m_browserService.getDatabaseGroups();
|
||||
const QJsonObject groups = browserService()->getDatabaseGroups();
|
||||
if (groups.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_GROUPS_FOUND);
|
||||
}
|
||||
@ -422,11 +432,10 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons
|
||||
|
||||
QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
@ -442,7 +451,7 @@ QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const Q
|
||||
}
|
||||
|
||||
QString group = decrypted.value("groupName").toString();
|
||||
const QJsonObject newGroup = m_browserService.createNewGroup(group);
|
||||
const QJsonObject newGroup = browserService()->createNewGroup(group);
|
||||
if (newGroup.isEmpty() || newGroup["name"].toString().isEmpty() || newGroup["uuid"].toString().isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP);
|
||||
}
|
||||
@ -524,38 +533,6 @@ QString BrowserAction::getErrorMessage(const int errorCode) const
|
||||
}
|
||||
}
|
||||
|
||||
QString BrowserAction::getReturnValue(const BrowserService::ReturnValue returnValue) const
|
||||
{
|
||||
switch (returnValue) {
|
||||
case BrowserService::ReturnValue::Success:
|
||||
return QString("success");
|
||||
case BrowserService::ReturnValue::Error:
|
||||
return QString("error");
|
||||
case BrowserService::ReturnValue::Canceled:
|
||||
return QString("canceled");
|
||||
}
|
||||
return QString("error");
|
||||
}
|
||||
|
||||
QString BrowserAction::getDatabaseHash()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QByteArray hash =
|
||||
QCryptographicHash::hash(m_browserService.getDatabaseRootUuid().toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||
return QString(hash);
|
||||
}
|
||||
|
||||
QString BrowserAction::getLegacyDatabaseHash()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QByteArray hash =
|
||||
QCryptographicHash::hash(
|
||||
(m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
|
||||
QCryptographicHash::Sha256)
|
||||
.toHex();
|
||||
return QString(hash);
|
||||
}
|
||||
|
||||
QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
@ -586,7 +563,6 @@ QJsonObject BrowserAction::decryptMessage(const QString& message, const QString&
|
||||
|
||||
QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QByteArray ma = plaintext.toUtf8();
|
||||
const QByteArray na = base64Decode(nonce);
|
||||
const QByteArray ca = base64Decode(m_clientPublicKey);
|
||||
@ -598,7 +574,7 @@ QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
|
||||
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
|
||||
|
||||
std::vector<unsigned char> e;
|
||||
e.resize(NATIVE_MSG_MAX_LENGTH);
|
||||
e.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QString();
|
||||
@ -614,7 +590,6 @@ QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
|
||||
|
||||
QByteArray BrowserAction::decrypt(const QString& encrypted, const QString& nonce)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QByteArray ma = base64Decode(encrypted);
|
||||
const QByteArray na = base64Decode(nonce);
|
||||
const QByteArray ca = base64Decode(m_clientPublicKey);
|
||||
@ -626,7 +601,7 @@ QByteArray BrowserAction::decrypt(const QString& encrypted, const QString& nonce
|
||||
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
|
||||
|
||||
std::vector<unsigned char> d;
|
||||
d.resize(NATIVE_MSG_MAX_LENGTH);
|
||||
d.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QByteArray();
|
||||
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -19,42 +18,16 @@
|
||||
#ifndef BROWSERACTION_H
|
||||
#define BROWSERACTION_H
|
||||
|
||||
#include "BrowserService.h"
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QtCore>
|
||||
#include <QString>
|
||||
|
||||
class BrowserAction : public QObject
|
||||
class BrowserAction
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum
|
||||
{
|
||||
ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
|
||||
ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
|
||||
ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
|
||||
ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
|
||||
ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
|
||||
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
|
||||
ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
|
||||
ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
|
||||
ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
|
||||
ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
|
||||
ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
|
||||
ERROR_KEEPASS_INCORRECT_ACTION = 12,
|
||||
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
|
||||
ERROR_KEEPASS_NO_URL_PROVIDED = 14,
|
||||
ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
|
||||
ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
|
||||
ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17
|
||||
};
|
||||
|
||||
public:
|
||||
BrowserAction(BrowserService& browserService);
|
||||
explicit BrowserAction() = default;
|
||||
~BrowserAction() = default;
|
||||
|
||||
QJsonObject readResponse(const QJsonObject& json);
|
||||
QJsonObject processClientMessage(const QJsonObject& json);
|
||||
|
||||
private:
|
||||
QJsonObject handleAction(const QJsonObject& json);
|
||||
@ -73,9 +46,6 @@ private:
|
||||
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
|
||||
QString getErrorMessage(const int errorCode) const;
|
||||
QString getReturnValue(const BrowserService::ReturnValue returnValue) const;
|
||||
QString getDatabaseHash();
|
||||
QString getLegacyDatabaseHash();
|
||||
|
||||
QString encryptMessage(const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject decryptMessage(const QString& message, const QString& nonce);
|
||||
@ -90,12 +60,10 @@ private:
|
||||
QString incrementNonce(const QString& nonce);
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
BrowserService& m_browserService;
|
||||
QString m_clientPublicKey;
|
||||
QString m_publicKey;
|
||||
QString m_secretKey;
|
||||
bool m_associated;
|
||||
bool m_associated = false;
|
||||
|
||||
friend class TestBrowser;
|
||||
};
|
||||
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserClients.h"
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonValue>
|
||||
|
||||
BrowserClients::BrowserClients(BrowserService& browserService)
|
||||
: m_mutex(QMutex::Recursive)
|
||||
, m_browserService(browserService)
|
||||
{
|
||||
m_clients.reserve(1000);
|
||||
}
|
||||
|
||||
QJsonObject BrowserClients::readResponse(const QByteArray& arr)
|
||||
{
|
||||
QJsonObject json;
|
||||
const QJsonObject message = byteArrayToJson(arr);
|
||||
const QString clientID = getClientID(message);
|
||||
|
||||
if (!clientID.isEmpty()) {
|
||||
const ClientPtr client = getClient(clientID);
|
||||
if (client->browserAction) {
|
||||
json = client->browserAction->readResponse(message);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QJsonObject BrowserClients::byteArrayToJson(const QByteArray& arr) const
|
||||
{
|
||||
QJsonObject json;
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
|
||||
if (doc.isObject()) {
|
||||
json = doc.object();
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QString BrowserClients::getClientID(const QJsonObject& json) const
|
||||
{
|
||||
return json["clientID"].toString();
|
||||
}
|
||||
|
||||
BrowserClients::ClientPtr BrowserClients::getClient(const QString& clientID)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (const auto& i : m_clients) {
|
||||
if (i->clientID.compare(clientID, Qt::CaseSensitive) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// clientID not found, create a new client
|
||||
QSharedPointer<BrowserAction> ba = QSharedPointer<BrowserAction>::create(m_browserService);
|
||||
ClientPtr client = ClientPtr::create(clientID, ba);
|
||||
m_clients.push_back(client);
|
||||
return m_clients.back();
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERCLIENTS_H
|
||||
#define BROWSERCLIENTS_H
|
||||
|
||||
#include "BrowserAction.h"
|
||||
#include <QJsonObject>
|
||||
#include <QLocalSocket>
|
||||
#include <QMutex>
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
|
||||
class BrowserClients
|
||||
{
|
||||
struct Client
|
||||
{
|
||||
Client(const QString& id, QSharedPointer<BrowserAction> ba)
|
||||
: clientID(id)
|
||||
, browserAction(ba)
|
||||
{
|
||||
}
|
||||
QString clientID;
|
||||
QSharedPointer<BrowserAction> browserAction;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<Client> ClientPtr;
|
||||
|
||||
public:
|
||||
BrowserClients(BrowserService& browserService);
|
||||
~BrowserClients() = default;
|
||||
|
||||
QJsonObject readResponse(const QByteArray& arr);
|
||||
|
||||
private:
|
||||
QJsonObject byteArrayToJson(const QByteArray& arr) const;
|
||||
QString getClientID(const QJsonObject& json) const;
|
||||
ClientPtr getClient(const QString& clientID);
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
QVector<ClientPtr> m_clients;
|
||||
BrowserService& m_browserService;
|
||||
};
|
||||
|
||||
#endif // BROWSERCLIENTS_H
|
107
src/browser/BrowserHost.cpp
Normal file
107
src/browser/BrowserHost.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserHost.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "BrowserShared.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QMutexLocker>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "sodium.h"
|
||||
#include <iostream>
|
||||
|
||||
BrowserHost::BrowserHost(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_localServer = new QLocalServer(this);
|
||||
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(proxyConnected()));
|
||||
}
|
||||
|
||||
BrowserHost::~BrowserHost()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void BrowserHost::start()
|
||||
{
|
||||
if (sodium_init() == -1) {
|
||||
qWarning() << "Failed to start browser service: libsodium failed to initialize!";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_localServer->isListening()) {
|
||||
m_localServer->listen(BrowserShared::localServerPath());
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserHost::stop()
|
||||
{
|
||||
m_socketList.clear();
|
||||
m_localServer->close();
|
||||
}
|
||||
|
||||
void BrowserHost::proxyConnected()
|
||||
{
|
||||
auto socket = m_localServer->nextPendingConnection();
|
||||
if (socket) {
|
||||
m_socketList.append(socket);
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(readProxyMessage()));
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(proxyDisconnected()));
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserHost::readProxyMessage()
|
||||
{
|
||||
QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
|
||||
if (!socket || socket->bytesAvailable() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
QJsonParseError error;
|
||||
auto json = QJsonDocument::fromJson(socket->readAll(), &error);
|
||||
if (json.isNull()) {
|
||||
qWarning() << "Failed to read proxy message: " << error.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
emit clientMessageReceived(json.object());
|
||||
}
|
||||
|
||||
void BrowserHost::sendClientMessage(const QJsonObject& json)
|
||||
{
|
||||
QString reply(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||
for (const auto socket : m_socketList) {
|
||||
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
|
||||
QByteArray arr = reply.toUtf8();
|
||||
socket->write(arr.constData(), arr.length());
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserHost::proxyDisconnected()
|
||||
{
|
||||
auto socket = qobject_cast<QLocalSocket*>(QObject::sender());
|
||||
m_socketList.removeOne(socket);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,29 +18,37 @@
|
||||
#ifndef NATIVEMESSAGINGHOST_H
|
||||
#define NATIVEMESSAGINGHOST_H
|
||||
|
||||
#include "NativeMessagingBase.h"
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class NativeMessagingHost : public NativeMessagingBase
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
|
||||
class BrowserHost : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NativeMessagingHost();
|
||||
~NativeMessagingHost() override;
|
||||
explicit BrowserHost(QObject* parent = nullptr);
|
||||
~BrowserHost() override;
|
||||
|
||||
public slots:
|
||||
void newLocalMessage();
|
||||
void deleteSocket();
|
||||
void socketStateChanged(QLocalSocket::LocalSocketState socketState);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void sendClientMessage(const QJsonObject& json);
|
||||
|
||||
signals:
|
||||
void clientMessageReceived(const QJsonObject& json);
|
||||
|
||||
private slots:
|
||||
void proxyConnected();
|
||||
void readProxyMessage();
|
||||
void proxyDisconnected();
|
||||
|
||||
private:
|
||||
void readNativeMessages() override;
|
||||
void readLength() override;
|
||||
bool readStdIn(const quint32 length) override;
|
||||
|
||||
private:
|
||||
QLocalSocket* m_localSocket;
|
||||
|
||||
Q_DISABLE_COPY(NativeMessagingHost)
|
||||
QPointer<QLocalServer> m_localServer;
|
||||
QList<QLocalSocket*> m_socketList;
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGHOST_H
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -26,8 +26,10 @@
|
||||
#include <QUuid>
|
||||
|
||||
#include "BrowserAccessControlDialog.h"
|
||||
#include "BrowserAction.h"
|
||||
#include "BrowserEntryConfig.h"
|
||||
#include "BrowserEntrySaveDialog.h"
|
||||
#include "BrowserHost.h"
|
||||
#include "BrowserService.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "core/Database.h"
|
||||
@ -58,34 +60,45 @@ const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnl
|
||||
// Multiple URL's
|
||||
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
|
||||
|
||||
BrowserService::BrowserService(DatabaseTabWidget* parent)
|
||||
: m_dbTabWidget(parent)
|
||||
Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
||||
|
||||
BrowserService::BrowserService()
|
||||
: QObject()
|
||||
, m_browserHost(new BrowserHost)
|
||||
, m_dialogActive(false)
|
||||
, m_bringToFrontRequested(false)
|
||||
, m_prevWindowState(WindowState::Normal)
|
||||
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
||||
{
|
||||
// Don't connect the signals when used from DatabaseSettingsWidgetBrowser (parent is nullptr)
|
||||
if (m_dbTabWidget) {
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
|
||||
connect(
|
||||
m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
|
||||
connect(m_dbTabWidget,
|
||||
SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
|
||||
this,
|
||||
SLOT(activateDatabaseChanged(DatabaseWidget*)));
|
||||
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
|
||||
setEnabled(browserSettings()->isEnabled());
|
||||
}
|
||||
|
||||
BrowserService* BrowserService::instance()
|
||||
{
|
||||
return s_browserService;
|
||||
}
|
||||
|
||||
void BrowserService::setEnabled(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
|
||||
if (browserSettings()->updateBinaryPath()) {
|
||||
browserSettings()->updateBinaryPaths();
|
||||
}
|
||||
|
||||
m_browserHost->start();
|
||||
} else {
|
||||
m_browserHost->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowserService::isDatabaseOpened() const
|
||||
{
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return false;
|
||||
if (m_currentDatabaseWidget) {
|
||||
return !m_currentDatabaseWidget->isLocked();
|
||||
}
|
||||
|
||||
return dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
||||
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BrowserService::openDatabase(bool triggerUnlock)
|
||||
@ -94,13 +107,7 @@ bool BrowserService::openDatabase(bool triggerUnlock)
|
||||
return false;
|
||||
}
|
||||
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
||||
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
|
||||
if (m_currentDatabaseWidget && !m_currentDatabaseWidget->isLocked()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -114,19 +121,20 @@ bool BrowserService::openDatabase(bool triggerUnlock)
|
||||
|
||||
void BrowserService::lockDatabase()
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
|
||||
if (m_currentDatabaseWidget) {
|
||||
m_currentDatabaseWidget->lock();
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode
|
||||
|| dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
|
||||
dbWidget->lock();
|
||||
QString BrowserService::getDatabaseHash(bool legacy)
|
||||
{
|
||||
if (legacy) {
|
||||
return QCryptographicHash::hash(
|
||||
(browserService()->getDatabaseRootUuid() + browserService()->getDatabaseRecycleBinUuid()).toUtf8(),
|
||||
QCryptographicHash::Sha256)
|
||||
.toHex();
|
||||
}
|
||||
return QCryptographicHash::hash(getDatabaseRootUuid().toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||
}
|
||||
|
||||
QString BrowserService::getDatabaseRootUuid()
|
||||
@ -180,9 +188,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group)
|
||||
return groupList;
|
||||
}
|
||||
|
||||
QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& selectedDb)
|
||||
QJsonObject BrowserService::getDatabaseGroups()
|
||||
{
|
||||
auto db = selectedDb ? selectedDb : getDatabase();
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
@ -208,15 +216,6 @@ QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& se
|
||||
|
||||
QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
||||
{
|
||||
QJsonObject result;
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this,
|
||||
"createNewGroup",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QJsonObject, result),
|
||||
Q_ARG(QString, groupName));
|
||||
return result;
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
@ -232,6 +231,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
||||
|
||||
// Group already exists
|
||||
if (group) {
|
||||
QJsonObject result;
|
||||
result["name"] = group->name();
|
||||
result["uuid"] = Tools::uuidToHex(group->uuid());
|
||||
return result;
|
||||
@ -245,7 +245,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
||||
MessageBox::Yes | MessageBox::No);
|
||||
|
||||
if (dialogResult != MessageBox::Yes) {
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
QString name, uuid;
|
||||
@ -279,6 +279,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
||||
previousGroup = tempGroup;
|
||||
}
|
||||
|
||||
QJsonObject result;
|
||||
result["name"] = name;
|
||||
result["uuid"] = uuid;
|
||||
return result;
|
||||
@ -286,25 +287,18 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
||||
|
||||
QString BrowserService::storeKey(const QString& key)
|
||||
{
|
||||
QString id;
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(
|
||||
this, "storeKey", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, id), Q_ARG(QString, key));
|
||||
return id;
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool contains;
|
||||
MessageBox::Button dialogResult = MessageBox::Cancel;
|
||||
auto dialogResult = MessageBox::Cancel;
|
||||
QString id;
|
||||
|
||||
do {
|
||||
QInputDialog keyDialog;
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
|
||||
connect(m_currentDatabaseWidget, SIGNAL(databaseLocked()), &keyDialog, SLOT(reject()));
|
||||
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
||||
keyDialog.setLabelText(tr("You have received an association request for the following database:\n%1\n\n"
|
||||
"Give the connection a unique name or ID, for example:\nchrome-laptop.")
|
||||
@ -353,28 +347,14 @@ QString BrowserService::getKey(const QString& id)
|
||||
return db->metadata()->customData()->value(ASSOCIATE_KEY_PREFIX + id);
|
||||
}
|
||||
|
||||
QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||
const QString& url,
|
||||
const QString& submitUrl,
|
||||
const QString& realm,
|
||||
const StringPairList& keyList,
|
||||
const bool httpAuth)
|
||||
{
|
||||
QJsonArray result;
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this,
|
||||
"findMatchingEntries",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QJsonArray, result),
|
||||
Q_ARG(QString, id),
|
||||
Q_ARG(QString, url),
|
||||
Q_ARG(QString, submitUrl),
|
||||
Q_ARG(QString, realm),
|
||||
Q_ARG(StringPairList, keyList),
|
||||
Q_ARG(bool, httpAuth));
|
||||
return result;
|
||||
}
|
||||
|
||||
Q_UNUSED(dbid);
|
||||
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
||||
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
|
||||
const QString host = QUrl(url).host();
|
||||
@ -425,18 +405,19 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
}
|
||||
|
||||
if (pwEntries.isEmpty()) {
|
||||
return QJsonArray();
|
||||
return {};
|
||||
}
|
||||
|
||||
// Ensure that database is not locked when the popup was visible
|
||||
if (!isDatabaseOpened()) {
|
||||
return QJsonArray();
|
||||
return {};
|
||||
}
|
||||
|
||||
// Sort results
|
||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||
|
||||
// Fill the list
|
||||
QJsonArray result;
|
||||
for (auto* entry : pwEntries) {
|
||||
result.append(prepareEntry(entry));
|
||||
}
|
||||
@ -444,7 +425,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
return result;
|
||||
}
|
||||
|
||||
void BrowserService::addEntry(const QString& id,
|
||||
void BrowserService::addEntry(const QString& dbid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& url,
|
||||
@ -454,21 +435,8 @@ void BrowserService::addEntry(const QString& id,
|
||||
const QString& groupUuid,
|
||||
const QSharedPointer<Database>& selectedDb)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this,
|
||||
"addEntry",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, id),
|
||||
Q_ARG(QString, login),
|
||||
Q_ARG(QString, password),
|
||||
Q_ARG(QString, url),
|
||||
Q_ARG(QString, submitUrl),
|
||||
Q_ARG(QString, realm),
|
||||
Q_ARG(QString, group),
|
||||
Q_ARG(QString, groupUuid),
|
||||
Q_ARG(QSharedPointer<Database>, selectedDb));
|
||||
}
|
||||
|
||||
// TODO: select database based on this key id
|
||||
Q_UNUSED(dbid);
|
||||
auto db = selectedDb ? selectedDb : selectedDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
@ -510,37 +478,25 @@ void BrowserService::addEntry(const QString& id,
|
||||
config.save(entry);
|
||||
}
|
||||
|
||||
BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
|
||||
bool BrowserService::updateEntry(const QString& dbid,
|
||||
const QString& uuid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& url,
|
||||
const QString& submitUrl)
|
||||
{
|
||||
ReturnValue result = ReturnValue::Error;
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this,
|
||||
"updateEntry",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(ReturnValue, result),
|
||||
Q_ARG(QString, id),
|
||||
Q_ARG(QString, uuid),
|
||||
Q_ARG(QString, login),
|
||||
Q_ARG(QString, password),
|
||||
Q_ARG(QString, url),
|
||||
Q_ARG(QString, submitUrl));
|
||||
}
|
||||
|
||||
// TODO: select database based on this key id
|
||||
Q_UNUSED(dbid);
|
||||
auto db = selectedDatabase();
|
||||
if (!db) {
|
||||
return ReturnValue::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
|
||||
if (!entry) {
|
||||
// If entry is not found for update, add a new one to the selected database
|
||||
addEntry(id, login, password, url, submitUrl, "", "", "", db);
|
||||
return ReturnValue::Success;
|
||||
addEntry(dbid, login, password, url, submitUrl, "", "", "", db);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the entry password is a reference. If so, update the original entry instead
|
||||
@ -549,16 +505,17 @@ BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
|
||||
if (!referenceUuid.isNull()) {
|
||||
entry = db->rootGroup()->findEntryByUuid(referenceUuid);
|
||||
if (!entry) {
|
||||
return ReturnValue::Error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString username = entry->username();
|
||||
if (username.isEmpty()) {
|
||||
return ReturnValue::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (username.compare(login, Qt::CaseSensitive) != 0
|
||||
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
|
||||
MessageBox::Button dialogResult = MessageBox::No;
|
||||
@ -580,9 +537,7 @@ BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
|
||||
}
|
||||
entry->setPassword(password);
|
||||
entry->endUpdate();
|
||||
result = ReturnValue::Success;
|
||||
} else {
|
||||
result = ReturnValue::Canceled;
|
||||
result = true;
|
||||
}
|
||||
|
||||
hideWindow();
|
||||
@ -646,17 +601,14 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& s
|
||||
// Get the list of databases to search
|
||||
QList<QSharedPointer<Database>> databases;
|
||||
if (browserSettings()->searchInAllDatabases()) {
|
||||
const int count = m_dbTabWidget->count();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
||||
if (const auto& db = dbWidget->database()) {
|
||||
if (databaseConnected(db)) {
|
||||
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
||||
auto db = dbWidget->database();
|
||||
if (db && databaseConnected(dbWidget->database())) {
|
||||
databases << db;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (const auto& db = getDatabase()) {
|
||||
} else {
|
||||
const auto& db = getDatabase();
|
||||
if (databaseConnected(db)) {
|
||||
databases << db;
|
||||
}
|
||||
@ -674,9 +626,8 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& s
|
||||
return entries;
|
||||
}
|
||||
|
||||
void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database>& currentDb)
|
||||
void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> db)
|
||||
{
|
||||
auto db = currentDb ? currentDb : getDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
@ -806,7 +757,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
m_dialogActive = true;
|
||||
BrowserAccessControlDialog accessControlDialog;
|
||||
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
|
||||
connect(m_currentDatabaseWidget, SIGNAL(databaseLocked()), &accessControlDialog, SLOT(reject()));
|
||||
connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) {
|
||||
auto entry = pwEntriesToConfirm[item->row()];
|
||||
BrowserEntryConfig config;
|
||||
@ -1103,10 +1054,8 @@ QString BrowserService::baseDomain(const QString& hostname) const
|
||||
|
||||
QSharedPointer<Database> BrowserService::getDatabase()
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
||||
if (const auto& db = dbWidget->database()) {
|
||||
return db;
|
||||
}
|
||||
if (m_currentDatabaseWidget) {
|
||||
return m_currentDatabaseWidget->database();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@ -1114,20 +1063,15 @@ QSharedPointer<Database> BrowserService::getDatabase()
|
||||
QSharedPointer<Database> BrowserService::selectedDatabase()
|
||||
{
|
||||
QList<DatabaseWidget*> databaseWidgets;
|
||||
for (int i = 0;; ++i) {
|
||||
auto* dbWidget = m_dbTabWidget->databaseWidgetFromIndex(i);
|
||||
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
||||
// Add only open databases
|
||||
if (dbWidget && !dbWidget->isLocked()) {
|
||||
databaseWidgets.push_back(dbWidget);
|
||||
continue;
|
||||
if (!dbWidget->isLocked()) {
|
||||
databaseWidgets << dbWidget;
|
||||
}
|
||||
|
||||
// Break out if dbStruct.dbWidget is nullptr
|
||||
break;
|
||||
}
|
||||
|
||||
BrowserEntrySaveDialog browserEntrySaveDialog;
|
||||
int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_dbTabWidget->currentDatabaseWidget());
|
||||
int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_currentDatabaseWidget);
|
||||
if (openDatabaseCount > 1) {
|
||||
int res = browserEntrySaveDialog.exec();
|
||||
if (res == QDialog::Accepted) {
|
||||
@ -1145,7 +1089,7 @@ QSharedPointer<Database> BrowserService::selectedDatabase()
|
||||
return getDatabase();
|
||||
}
|
||||
|
||||
bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name) const
|
||||
bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
|
||||
{
|
||||
if (entry->attributes()->contains(name)) {
|
||||
QString attr = entry->attributes()->value(name);
|
||||
@ -1160,7 +1104,7 @@ bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
|
||||
return false;
|
||||
}
|
||||
|
||||
int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Database>& db) const
|
||||
int BrowserService::moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db)
|
||||
{
|
||||
int keyCounter = 0;
|
||||
for (const auto& key : entry->attributes()->keys()) {
|
||||
@ -1179,14 +1123,9 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Data
|
||||
return keyCounter;
|
||||
}
|
||||
|
||||
bool BrowserService::checkLegacySettings()
|
||||
bool BrowserService::checkLegacySettings(QSharedPointer<Database> db)
|
||||
{
|
||||
if (!browserSettings()->isEnabled() || browserSettings()->noMigrationPrompt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
if (!db || !browserSettings()->isEnabled() || browserSettings()->noMigrationPrompt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1272,7 +1211,9 @@ void BrowserService::raiseWindow(const bool force)
|
||||
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
emit databaseLocked();
|
||||
QJsonObject msg;
|
||||
msg["action"] = QString("database-locked");
|
||||
m_browserHost->sendClientMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1283,22 +1224,43 @@ void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
|
||||
hideWindow();
|
||||
m_bringToFrontRequested = false;
|
||||
}
|
||||
emit databaseUnlocked();
|
||||
|
||||
if (checkLegacySettings()) {
|
||||
convertAttributesToCustomData();
|
||||
QJsonObject msg;
|
||||
msg["action"] = QString("database-unlocked");
|
||||
m_browserHost->sendClientMessage(msg);
|
||||
|
||||
auto db = dbWidget->database();
|
||||
if (checkLegacySettings(db)) {
|
||||
convertAttributesToCustomData(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
|
||||
void BrowserService::activeDatabaseChanged(DatabaseWidget* dbWidget)
|
||||
{
|
||||
m_currentDatabaseWidget = dbWidget;
|
||||
if (dbWidget) {
|
||||
auto currentMode = dbWidget->currentMode();
|
||||
if (currentMode == DatabaseWidget::Mode::ViewMode || currentMode == DatabaseWidget::Mode::EditMode) {
|
||||
emit databaseUnlocked();
|
||||
if (dbWidget->isLocked()) {
|
||||
databaseLocked(dbWidget);
|
||||
} else {
|
||||
emit databaseLocked();
|
||||
databaseUnlocked(dbWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::processClientMessage(const QJsonObject& message)
|
||||
{
|
||||
auto clientID = message["clientID"].toString();
|
||||
if (clientID.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new client action if we haven't seen this id yet
|
||||
if (!m_browserClients.contains(clientID)) {
|
||||
m_browserClients.insert(clientID, QSharedPointer<BrowserAction>::create());
|
||||
}
|
||||
|
||||
auto& action = m_browserClients.value(clientID);
|
||||
auto response = action->processClientMessage(message);
|
||||
m_browserHost->sendClientMessage(response);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -21,8 +21,9 @@
|
||||
#define BROWSERSERVICE_H
|
||||
|
||||
#include "core/Entry.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QSharedPointer>
|
||||
#include <QtCore>
|
||||
|
||||
typedef QPair<QString, QString> StringPair;
|
||||
@ -33,28 +34,33 @@ enum
|
||||
max_length = 16 * 1024
|
||||
};
|
||||
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
class BrowserHost;
|
||||
class BrowserAction;
|
||||
|
||||
class BrowserService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ReturnValue
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
Canceled
|
||||
};
|
||||
explicit BrowserService();
|
||||
static BrowserService* instance();
|
||||
|
||||
explicit BrowserService(DatabaseTabWidget* parent);
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
QString getKey(const QString& id);
|
||||
QString storeKey(const QString& key);
|
||||
QString getDatabaseHash(bool legacy = false);
|
||||
|
||||
bool isDatabaseOpened() const;
|
||||
bool openDatabase(bool triggerUnlock);
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
QJsonObject getDatabaseGroups(const QSharedPointer<Database>& selectedDb = {});
|
||||
void lockDatabase();
|
||||
|
||||
QJsonObject getDatabaseGroups();
|
||||
QJsonObject createNewGroup(const QString& groupName);
|
||||
QString getKey(const QString& id);
|
||||
void addEntry(const QString& id,
|
||||
|
||||
void addEntry(const QString& dbid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& url,
|
||||
@ -63,11 +69,22 @@ public:
|
||||
const QString& group,
|
||||
const QString& groupUuid,
|
||||
const QSharedPointer<Database>& selectedDb = {});
|
||||
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl);
|
||||
QList<Entry*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
|
||||
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
|
||||
bool updateEntry(const QString& dbid,
|
||||
const QString& uuid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& url,
|
||||
const QString& submitUrl);
|
||||
|
||||
QJsonArray findMatchingEntries(const QString& dbid,
|
||||
const QString& url,
|
||||
const QString& submitUrl,
|
||||
const QString& realm,
|
||||
const StringPairList& keyList,
|
||||
const bool httpAuth = false);
|
||||
|
||||
static void convertAttributesToCustomData(QSharedPointer<Database> db);
|
||||
|
||||
public:
|
||||
static const QString KEEPASSXCBROWSER_NAME;
|
||||
static const QString KEEPASSXCBROWSER_OLD_NAME;
|
||||
static const QString ASSOCIATE_KEY_PREFIX;
|
||||
@ -78,28 +95,12 @@ public:
|
||||
static const QString ADDITIONAL_URL;
|
||||
|
||||
public slots:
|
||||
QJsonArray findMatchingEntries(const QString& id,
|
||||
const QString& url,
|
||||
const QString& submitUrl,
|
||||
const QString& realm,
|
||||
const StringPairList& keyList,
|
||||
const bool httpAuth = false);
|
||||
QString storeKey(const QString& key);
|
||||
ReturnValue updateEntry(const QString& id,
|
||||
const QString& uuid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& url,
|
||||
const QString& submitUrl);
|
||||
void databaseLocked(DatabaseWidget* dbWidget);
|
||||
void databaseUnlocked(DatabaseWidget* dbWidget);
|
||||
void activateDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
void lockDatabase();
|
||||
void activeDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
|
||||
signals:
|
||||
void databaseLocked();
|
||||
void databaseUnlocked();
|
||||
void databaseChanged();
|
||||
private slots:
|
||||
void processClientMessage(const QJsonObject& message);
|
||||
|
||||
private:
|
||||
enum Access
|
||||
@ -116,7 +117,8 @@ private:
|
||||
Hidden
|
||||
};
|
||||
|
||||
private:
|
||||
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl);
|
||||
QList<Entry*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
|
||||
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
|
||||
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
const QString& url,
|
||||
@ -125,6 +127,7 @@ private:
|
||||
const QString& realm,
|
||||
const bool httpAuth);
|
||||
QJsonObject prepareEntry(const Entry* entry);
|
||||
QJsonArray getChildrenFromGroup(Group* group);
|
||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
||||
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
||||
int
|
||||
@ -135,21 +138,35 @@ private:
|
||||
QString baseDomain(const QString& hostname) const;
|
||||
QSharedPointer<Database> getDatabase();
|
||||
QSharedPointer<Database> selectedDatabase();
|
||||
QJsonArray getChildrenFromGroup(Group* group);
|
||||
bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
|
||||
int moveKeysToCustomData(Entry* entry, const QSharedPointer<Database>& db) const;
|
||||
bool checkLegacySettings();
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
|
||||
bool checkLegacySettings(QSharedPointer<Database> db);
|
||||
|
||||
void hideWindow() const;
|
||||
void raiseWindow(const bool force = false);
|
||||
|
||||
private:
|
||||
DatabaseTabWidget* const m_dbTabWidget;
|
||||
static bool moveSettingsToCustomData(Entry* entry, const QString& name);
|
||||
static int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db);
|
||||
|
||||
QPointer<BrowserHost> m_browserHost;
|
||||
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;
|
||||
|
||||
bool m_dialogActive;
|
||||
bool m_bringToFrontRequested;
|
||||
WindowState m_prevWindowState;
|
||||
QUuid m_keepassBrowserUUID;
|
||||
|
||||
QPointer<DatabaseWidget> m_currentDatabaseWidget;
|
||||
|
||||
Q_DISABLE_COPY(BrowserService);
|
||||
|
||||
friend class TestBrowser;
|
||||
};
|
||||
|
||||
static inline BrowserService* browserService()
|
||||
{
|
||||
return BrowserService::instance();
|
||||
}
|
||||
|
||||
#endif // BROWSERSERVICE_H
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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()
|
||||
|
49
src/browser/BrowserSettingsPage.cpp
Normal file
49
src/browser/BrowserSettingsPage.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserSettingsPage.h"
|
||||
|
||||
#include "BrowserService.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "BrowserSettingsWidget.h"
|
||||
#include "core/Resources.h"
|
||||
|
||||
QString BrowserSettingsPage::name()
|
||||
{
|
||||
return QObject::tr("Browser Integration");
|
||||
}
|
||||
|
||||
QIcon BrowserSettingsPage::icon()
|
||||
{
|
||||
return Resources::instance()->icon("internet-web-browser");
|
||||
}
|
||||
|
||||
QWidget* BrowserSettingsPage::createWidget()
|
||||
{
|
||||
return new BrowserSettingsWidget();
|
||||
}
|
||||
|
||||
void BrowserSettingsPage::loadSettings(QWidget* widget)
|
||||
{
|
||||
qobject_cast<BrowserSettingsWidget*>(widget)->loadSettings();
|
||||
}
|
||||
|
||||
void BrowserSettingsPage::saveSettings(QWidget* widget)
|
||||
{
|
||||
qobject_cast<BrowserSettingsWidget*>(widget)->saveSettings();
|
||||
browserService()->setEnabled(browserSettings()->isEnabled());
|
||||
}
|
36
src/browser/BrowserSettingsPage.h
Normal file
36
src/browser/BrowserSettingsPage.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_BROWSERSETTINGSPAGE_H
|
||||
#define KEEPASSXC_BROWSERSETTINGSPAGE_H
|
||||
|
||||
#include "gui/ApplicationSettingsWidget.h"
|
||||
|
||||
class BrowserSettingsPage : public ISettingsPage
|
||||
{
|
||||
public:
|
||||
explicit BrowserSettingsPage() = default;
|
||||
~BrowserSettingsPage() override = default;
|
||||
|
||||
QString name() override;
|
||||
QIcon icon() override;
|
||||
QWidget* createWidget() override;
|
||||
void loadSettings(QWidget* widget) override;
|
||||
void saveSettings(QWidget* widget) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_BROWSERSETTINGSPAGE_H
|
@ -1,7 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -17,8 +15,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserOptionDialog.h"
|
||||
#include "ui_BrowserOptionDialog.h"
|
||||
#include "BrowserSettingsWidget.h"
|
||||
#include "ui_BrowserSettingsWidget.h"
|
||||
|
||||
#include "BrowserSettings.h"
|
||||
#include "config-keepassx.h"
|
||||
@ -26,9 +24,9 @@
|
||||
|
||||
#include <QFileDialog>
|
||||
|
||||
BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
||||
BrowserSettingsWidget::BrowserSettingsWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::BrowserOptionDialog())
|
||||
, m_ui(new Ui::BrowserSettingsWidget())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
@ -52,13 +50,9 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
||||
snapInstructions));
|
||||
// clang-format on
|
||||
|
||||
m_ui->scriptWarningWidget->setVisible(false);
|
||||
m_ui->scriptWarningWidget->setAutoHideTimeout(-1);
|
||||
|
||||
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
|
||||
MessageWidget::Warning);
|
||||
m_ui->warningWidget->setCloseButtonVisible(false);
|
||||
m_ui->warningWidget->setAutoHideTimeout(-1);
|
||||
m_ui->warningWidget->setAnimate(false);
|
||||
|
||||
m_ui->tabWidget->setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||
connect(m_ui->enableBrowserSupport, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool)));
|
||||
@ -67,6 +61,8 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
||||
m_ui->customProxyLocationBrowseButton->setEnabled(m_ui->useCustomProxy->isChecked());
|
||||
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocation, SLOT(setEnabled(bool)));
|
||||
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocationBrowseButton, SLOT(setEnabled(bool)));
|
||||
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), SLOT(validateCustomProxyLocation()));
|
||||
connect(m_ui->customProxyLocation, SIGNAL(editingFinished()), SLOT(validateCustomProxyLocation()));
|
||||
connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
|
||||
|
||||
#ifndef Q_OS_LINUX
|
||||
@ -86,11 +82,11 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
||||
m_ui->browserGlobalWarningWidget->setVisible(false);
|
||||
}
|
||||
|
||||
BrowserOptionDialog::~BrowserOptionDialog()
|
||||
BrowserSettingsWidget::~BrowserSettingsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::loadSettings()
|
||||
void BrowserSettingsWidget::loadSettings()
|
||||
{
|
||||
auto settings = browserSettings();
|
||||
m_ui->enableBrowserSupport->setChecked(settings->isEnabled());
|
||||
@ -116,43 +112,39 @@ void BrowserOptionDialog::loadSettings()
|
||||
m_ui->searchInAllDatabases->setChecked(settings->searchInAllDatabases());
|
||||
m_ui->supportKphFields->setChecked(settings->supportKphFields());
|
||||
m_ui->noMigrationPrompt->setChecked(settings->noMigrationPrompt());
|
||||
m_ui->supportBrowserProxy->setChecked(settings->supportBrowserProxy());
|
||||
m_ui->useCustomProxy->setChecked(settings->useCustomProxy());
|
||||
m_ui->customProxyLocation->setText(settings->customProxyLocation());
|
||||
m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath());
|
||||
m_ui->allowExpiredCredentials->setChecked(settings->allowExpiredCredentials());
|
||||
m_ui->chromeSupport->setChecked(settings->chromeSupport());
|
||||
m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
|
||||
m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
|
||||
m_ui->edgeSupport->setChecked(settings->edgeSupport());
|
||||
m_ui->chromeSupport->setChecked(settings->browserSupport(BrowserShared::CHROME));
|
||||
m_ui->chromiumSupport->setChecked(settings->browserSupport(BrowserShared::CHROMIUM));
|
||||
m_ui->firefoxSupport->setChecked(settings->browserSupport(BrowserShared::FIREFOX));
|
||||
m_ui->edgeSupport->setChecked(settings->browserSupport(BrowserShared::EDGE));
|
||||
#ifndef Q_OS_WIN
|
||||
m_ui->braveSupport->setChecked(settings->braveSupport());
|
||||
m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport());
|
||||
m_ui->torBrowserSupport->setChecked(settings->torBrowserSupport());
|
||||
m_ui->braveSupport->setChecked(settings->browserSupport(BrowserShared::BRAVE));
|
||||
m_ui->vivaldiSupport->setChecked(settings->browserSupport(BrowserShared::VIVALDI));
|
||||
m_ui->torBrowserSupport->setChecked(settings->browserSupport(BrowserShared::TOR_BROWSER));
|
||||
#endif
|
||||
#ifndef Q_OS_LINUX
|
||||
m_ui->snapWarningLabel->setVisible(false);
|
||||
#endif
|
||||
|
||||
// TODO: Enable when Linux version is released
|
||||
// TODO: Enable Edge support when Linux version is released
|
||||
#ifdef Q_OS_LINUX
|
||||
m_ui->edgeSupport->setChecked(false);
|
||||
m_ui->edgeSupport->setEnabled(false);
|
||||
#endif
|
||||
|
||||
#if defined(KEEPASSXC_DIST_APPIMAGE)
|
||||
m_ui->supportBrowserProxy->setChecked(true);
|
||||
m_ui->supportBrowserProxy->setEnabled(false);
|
||||
#elif defined(KEEPASSXC_DIST_SNAP)
|
||||
#ifdef KEEPASSXC_DIST_SNAP
|
||||
// Disable settings that will not work
|
||||
m_ui->supportBrowserProxy->setChecked(true);
|
||||
m_ui->supportBrowserProxy->setEnabled(false);
|
||||
m_ui->useCustomProxy->setChecked(false);
|
||||
m_ui->useCustomProxy->setEnabled(false);
|
||||
m_ui->useCustomProxy->setVisible(false);
|
||||
m_ui->customProxyLocation->setVisible(false);
|
||||
m_ui->customProxyLocationBrowseButton->setVisible(false);
|
||||
m_ui->browsersGroupBox->setVisible(false);
|
||||
m_ui->browsersGroupBox->setEnabled(false);
|
||||
m_ui->updateBinaryPath->setChecked(false);
|
||||
m_ui->updateBinaryPath->setEnabled(false);
|
||||
m_ui->updateBinaryPath->setVisible(false);
|
||||
// Show notice to user
|
||||
m_ui->browserGlobalWarningWidget->showMessage(tr("Please see special instructions for browser extension use below"),
|
||||
MessageWidget::Warning);
|
||||
@ -160,23 +152,23 @@ void BrowserOptionDialog::loadSettings()
|
||||
m_ui->browserGlobalWarningWidget->setAutoHideTimeout(-1);
|
||||
#endif
|
||||
|
||||
// Check for native messaging host location errors
|
||||
QString path;
|
||||
if (!settings->checkIfProxyExists(path)) {
|
||||
auto text =
|
||||
tr("<b>Warning</b>, the keepassxc-proxy application was not found!"
|
||||
"<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options."
|
||||
"<br />Browser integration WILL NOT WORK without the proxy application."
|
||||
"<br />Expected Path: %1")
|
||||
.arg(path);
|
||||
m_ui->scriptWarningWidget->showMessage(text, MessageWidget::Warning);
|
||||
m_ui->scriptWarningWidget->setVisible(true);
|
||||
validateCustomProxyLocation();
|
||||
}
|
||||
|
||||
void BrowserSettingsWidget::validateCustomProxyLocation()
|
||||
{
|
||||
auto path = m_ui->customProxyLocation->text();
|
||||
if (m_ui->useCustomProxy->isChecked() && !QFile::exists(path)) {
|
||||
m_ui->warningWidget->showMessage(tr("<b>Error:</b> The custom proxy location cannot be found!"
|
||||
"<br/>Browser integration WILL NOT WORK without the proxy application."),
|
||||
MessageWidget::Error);
|
||||
} else {
|
||||
m_ui->scriptWarningWidget->setVisible(false);
|
||||
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
|
||||
MessageWidget::Warning);
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::saveSettings()
|
||||
void BrowserSettingsWidget::saveSettings()
|
||||
{
|
||||
auto settings = browserSettings();
|
||||
settings->setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||
@ -186,7 +178,6 @@ void BrowserOptionDialog::saveSettings()
|
||||
settings->setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
|
||||
settings->setSortByUsername(m_ui->sortByUsername->isChecked());
|
||||
|
||||
settings->setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
|
||||
settings->setUseCustomProxy(m_ui->useCustomProxy->isChecked());
|
||||
settings->setCustomProxyLocation(m_ui->customProxyLocation->text());
|
||||
|
||||
@ -199,18 +190,18 @@ void BrowserOptionDialog::saveSettings()
|
||||
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
|
||||
settings->setNoMigrationPrompt(m_ui->noMigrationPrompt->isChecked());
|
||||
|
||||
settings->setChromeSupport(m_ui->chromeSupport->isChecked());
|
||||
settings->setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
||||
settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
||||
settings->setEdgeSupport(m_ui->edgeSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::CHROME, m_ui->chromeSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::CHROMIUM, m_ui->chromiumSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::FIREFOX, m_ui->firefoxSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::EDGE, m_ui->edgeSupport->isChecked());
|
||||
#ifndef Q_OS_WIN
|
||||
settings->setBraveSupport(m_ui->braveSupport->isChecked());
|
||||
settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
||||
settings->setTorBrowserSupport(m_ui->torBrowserSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::BRAVE, m_ui->braveSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::VIVALDI, m_ui->vivaldiSupport->isChecked());
|
||||
settings->setBrowserSupport(BrowserShared::TOR_BROWSER, m_ui->torBrowserSupport->isChecked());
|
||||
#endif
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::showProxyLocationFileDialog()
|
||||
void BrowserSettingsWidget::showProxyLocationFileDialog()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QString fileTypeFilter(QString("%1 (*.exe);;%2 (*.*)").arg(tr("Executable Files"), tr("All Files")));
|
||||
@ -222,4 +213,5 @@ void BrowserOptionDialog::showProxyLocationFileDialog()
|
||||
QFileInfo(QCoreApplication::applicationDirPath()).filePath(),
|
||||
fileTypeFilter);
|
||||
m_ui->customProxyLocation->setText(proxyLocation);
|
||||
validateCustomProxyLocation();
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -17,8 +15,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSEROPTIONDIALOG_H
|
||||
#define BROWSEROPTIONDIALOG_H
|
||||
#ifndef BROWSERSETTINGSWIDGET_H
|
||||
#define BROWSERSETTINGSWIDGET_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
@ -26,16 +24,16 @@
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class BrowserOptionDialog;
|
||||
class BrowserSettingsWidget;
|
||||
}
|
||||
|
||||
class BrowserOptionDialog : public QWidget
|
||||
class BrowserSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserOptionDialog(QWidget* parent = nullptr);
|
||||
~BrowserOptionDialog();
|
||||
explicit BrowserSettingsWidget(QWidget* parent = nullptr);
|
||||
~BrowserSettingsWidget();
|
||||
|
||||
public slots:
|
||||
void loadSettings();
|
||||
@ -43,9 +41,10 @@ public slots:
|
||||
|
||||
private slots:
|
||||
void showProxyLocationFileDialog();
|
||||
void validateCustomProxyLocation();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::BrowserOptionDialog> m_ui;
|
||||
QScopedPointer<Ui::BrowserSettingsWidget> m_ui;
|
||||
};
|
||||
|
||||
#endif // BROWSEROPTIONDIALOG_H
|
||||
#endif // BROWSERSETTINGSWIDGET_H
|
24
src/browser/BrowserOptionDialog.ui → src/browser/BrowserSettingsWidget.ui
Executable file → Normal file
24
src/browser/BrowserOptionDialog.ui → src/browser/BrowserSettingsWidget.ui
Executable file → Normal file
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BrowserOptionDialog</class>
|
||||
<widget class="QWidget" name="BrowserOptionDialog">
|
||||
<class>BrowserSettingsWidget</class>
|
||||
<widget class="QWidget" name="BrowserSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@ -49,16 +49,6 @@
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="scriptWarningWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="snapWarningLabel">
|
||||
<property name="text">
|
||||
@ -351,16 +341,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportBrowserProxy">
|
||||
<property name="toolTip">
|
||||
<string>Support a proxy application between KeePassXC and browser extension.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use a proxy application between KeePassXC and browser extension</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useCustomProxy">
|
||||
<property name="toolTip">
|
46
src/browser/BrowserShared.cpp
Normal file
46
src/browser/BrowserShared.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserShared.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStandardPaths>
|
||||
#include <QVariant>
|
||||
|
||||
namespace BrowserShared
|
||||
{
|
||||
QString localServerPath()
|
||||
{
|
||||
const auto appName = qApp->property("KPXC_QUALIFIED_APPNAME").toString();
|
||||
const auto serverName = QStringLiteral("/%1.BrowserServer").arg(appName);
|
||||
#if defined(KEEPASSXC_DIST_SNAP)
|
||||
return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverName;
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
// Use XDG_RUNTIME_DIR instead of /tmp if it's available
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||
return path.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverName
|
||||
: path + serverName;
|
||||
#elif defined(Q_OS_WIN)
|
||||
// Windows uses named pipes
|
||||
return serverName;
|
||||
#else // Q_OS_MACOS and others
|
||||
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverName;
|
||||
#endif
|
||||
}
|
||||
} // namespace BrowserShared
|
42
src/browser/BrowserShared.h
Normal file
42
src/browser/BrowserShared.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_BROWSERSHARED_H
|
||||
#define KEEPASSXC_BROWSERSHARED_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace BrowserShared
|
||||
{
|
||||
constexpr int NATIVEMSG_MAX_LENGTH = 1024 * 1024;
|
||||
|
||||
enum SupportedBrowsers : int
|
||||
{
|
||||
CHROME = 0,
|
||||
CHROMIUM,
|
||||
FIREFOX,
|
||||
VIVALDI,
|
||||
TOR_BROWSER,
|
||||
BRAVE,
|
||||
EDGE,
|
||||
MAX_SUPPORTED
|
||||
};
|
||||
|
||||
QString localServerPath();
|
||||
} // namespace BrowserShared
|
||||
|
||||
#endif // KEEPASSXC_BROWSERSHARED_H
|
@ -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})
|
||||
|
@ -1,359 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HostInstaller.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStandardPaths>
|
||||
|
||||
HostInstaller::HostInstaller()
|
||||
: HOST_NAME("org.keepassxc.keepassxc_browser")
|
||||
, ALLOWED_EXTENSIONS(QStringList() << "keepassxc-browser@keepassxc.org")
|
||||
, ALLOWED_ORIGINS(QStringList() << "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"
|
||||
<< "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/")
|
||||
#if defined(Q_OS_MACOS)
|
||||
, TARGET_DIR_CHROME("/Library/Application Support/Google/Chrome/NativeMessagingHosts")
|
||||
, TARGET_DIR_CHROMIUM("/Library/Application Support/Chromium/NativeMessagingHosts")
|
||||
, TARGET_DIR_FIREFOX("/Library/Application Support/Mozilla/NativeMessagingHosts")
|
||||
, TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts")
|
||||
, TARGET_DIR_TOR_BROWSER("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts")
|
||||
, TARGET_DIR_BRAVE("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts")
|
||||
, TARGET_DIR_EDGE("/Library/Application Support/Microsoft Edge/NativeMessagingHosts")
|
||||
#elif defined(Q_OS_WIN)
|
||||
// clang-format off
|
||||
, TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
||||
, TARGET_DIR_CHROMIUM("HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
||||
// clang-format on
|
||||
, TARGET_DIR_FIREFOX("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
||||
, TARGET_DIR_VIVALDI(TARGET_DIR_CHROME)
|
||||
, TARGET_DIR_TOR_BROWSER(TARGET_DIR_FIREFOX)
|
||||
, TARGET_DIR_BRAVE(TARGET_DIR_CHROME)
|
||||
, TARGET_DIR_EDGE(
|
||||
"HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
||||
#else
|
||||
, TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts")
|
||||
, TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts")
|
||||
, TARGET_DIR_FIREFOX("/.mozilla/native-messaging-hosts")
|
||||
, TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts")
|
||||
, TARGET_DIR_TOR_BROWSER("/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts")
|
||||
, TARGET_DIR_BRAVE("/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts")
|
||||
, TARGET_DIR_EDGE("/.config/microsoftedge/NativeMessagingHosts")
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the selected browser has native messaging host properly installed
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return bool Script is installed correctly
|
||||
*/
|
||||
bool HostInstaller::checkIfInstalled(SupportedBrowsers browser)
|
||||
{
|
||||
QString fileName = getPath(browser);
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
return registryEntryFound(settings);
|
||||
#else
|
||||
return QFile::exists(fileName);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if keepassxc-proxy location is found
|
||||
*
|
||||
* @param proxy Is keepassxc-proxy enabled
|
||||
* @param location Custom proxy location
|
||||
* @param path The path is set here and returned to the caller
|
||||
* @return bool
|
||||
*/
|
||||
bool HostInstaller::checkIfProxyExists(const bool& proxy, const QString& location, QString& path) const
|
||||
{
|
||||
QString fileName = getProxyPath(proxy, location);
|
||||
path = fileName;
|
||||
return QFile::exists(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs native messaging JSON script for the selected browser
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @param enabled Is browser integration enabled
|
||||
* @param proxy Is keepassxc-proxy enabled
|
||||
* @param location Custom proxy location
|
||||
*/
|
||||
void HostInstaller::installBrowser(SupportedBrowsers browser,
|
||||
const bool& enabled,
|
||||
const bool& proxy,
|
||||
const QString& location)
|
||||
{
|
||||
if (enabled) {
|
||||
#ifdef Q_OS_WIN
|
||||
// Create a registry key
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
settings.setValue("Default", getPath(browser));
|
||||
#endif
|
||||
// Always create the script file
|
||||
QJsonObject script = constructFile(browser, proxy, location);
|
||||
if (!saveFile(browser, script)) {
|
||||
QMessageBox::critical(nullptr,
|
||||
tr("KeePassXC: Cannot save file!"),
|
||||
tr("Cannot save the native messaging script file."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
} else {
|
||||
// Remove the script file
|
||||
QString fileName = getPath(browser);
|
||||
QFile::remove(fileName);
|
||||
#ifdef Q_OS_WIN
|
||||
// Remove the registry entry
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
settings.remove("Default");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the paths to native messaging host for each browser that has been enabled
|
||||
*
|
||||
* @param proxy Is keepassxc-proxy enabled
|
||||
* @param location Custom proxy location
|
||||
*/
|
||||
void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
|
||||
{
|
||||
for (int i = 0; i <= SupportedBrowsers::EDGE; ++i) {
|
||||
if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
|
||||
installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target path for each browser. Windows uses a registry path instead of a file path
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString Current target path for the selected browser
|
||||
*/
|
||||
QString HostInstaller::getTargetPath(SupportedBrowsers browser) const
|
||||
{
|
||||
switch (browser) {
|
||||
case SupportedBrowsers::CHROME:
|
||||
return TARGET_DIR_CHROME;
|
||||
case SupportedBrowsers::CHROMIUM:
|
||||
return TARGET_DIR_CHROMIUM;
|
||||
case SupportedBrowsers::FIREFOX:
|
||||
return TARGET_DIR_FIREFOX;
|
||||
case SupportedBrowsers::VIVALDI:
|
||||
return TARGET_DIR_VIVALDI;
|
||||
case SupportedBrowsers::TOR_BROWSER:
|
||||
return TARGET_DIR_TOR_BROWSER;
|
||||
case SupportedBrowsers::BRAVE:
|
||||
return TARGET_DIR_BRAVE;
|
||||
case SupportedBrowsers::EDGE:
|
||||
return TARGET_DIR_EDGE;
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the browser name
|
||||
* Needed for Windows to separate Chromium- or Firefox-based scripts
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString Name of the selected browser
|
||||
*/
|
||||
QString HostInstaller::getBrowserName(SupportedBrowsers browser) const
|
||||
{
|
||||
switch (browser) {
|
||||
case SupportedBrowsers::CHROME:
|
||||
return "chrome";
|
||||
case SupportedBrowsers::CHROMIUM:
|
||||
return "chromium";
|
||||
case SupportedBrowsers::FIREFOX:
|
||||
return "firefox";
|
||||
case SupportedBrowsers::VIVALDI:
|
||||
return "vivaldi";
|
||||
case SupportedBrowsers::TOR_BROWSER:
|
||||
return "tor-browser";
|
||||
case SupportedBrowsers::BRAVE:
|
||||
return "brave";
|
||||
case SupportedBrowsers::EDGE:
|
||||
return "edge";
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of native messaging JSON script for the selected browser
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString JSON script path for the selected browser
|
||||
*/
|
||||
QString HostInstaller::getPath(SupportedBrowsers browser) const
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// If portable settings file exists save the JSON scripts to application folder
|
||||
QString userPath;
|
||||
QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini";
|
||||
if (QFile::exists(portablePath)) {
|
||||
userPath = QCoreApplication::applicationDirPath();
|
||||
} else {
|
||||
userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
|
||||
}
|
||||
|
||||
QString winPath = QString("%1/%2_%3.json").arg(userPath, HOST_NAME, getBrowserName(browser));
|
||||
winPath.replace("/", "\\");
|
||||
return winPath;
|
||||
#else
|
||||
QString path = getTargetPath(browser);
|
||||
return QString("%1%2/%3.json").arg(QDir::homePath(), path, HOST_NAME);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the installation directory for JSON script file (application install path)
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString Install path
|
||||
*/
|
||||
QString HostInstaller::getInstallDir(SupportedBrowsers browser) const
|
||||
{
|
||||
QString path = getTargetPath(browser);
|
||||
#ifdef Q_OS_WIN
|
||||
return QCoreApplication::applicationDirPath();
|
||||
#else
|
||||
return QString("%1%2").arg(QDir::homePath(), path);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to keepassxc-proxy binary
|
||||
*
|
||||
* @param proxy Is keepassxc-proxy used with KeePassXC
|
||||
* @param location Custom proxy path
|
||||
* @return path Path to keepassxc-proxy
|
||||
*/
|
||||
QString HostInstaller::getProxyPath(const bool& proxy, const QString& location) const
|
||||
{
|
||||
QString path;
|
||||
#ifdef KEEPASSXC_DIST_APPIMAGE
|
||||
if (proxy && !location.isEmpty()) {
|
||||
path = location;
|
||||
} else {
|
||||
path = QProcessEnvironment::systemEnvironment().value("APPIMAGE");
|
||||
}
|
||||
#else
|
||||
if (proxy) {
|
||||
if (!location.isEmpty()) {
|
||||
path = location;
|
||||
} else {
|
||||
path = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath();
|
||||
path.append("/keepassxc-proxy");
|
||||
#ifdef Q_OS_WIN
|
||||
path.append(".exe");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
path = QFileInfo(QCoreApplication::applicationFilePath()).absoluteFilePath();
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
path.replace("/", "\\");
|
||||
#endif
|
||||
|
||||
#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the JSON script file used with native messaging
|
||||
*
|
||||
* @param browser Browser (Chromium- and Firefox-based browsers need a different parameters for the script)
|
||||
* @param proxy Is keepassxc-proxy used with KeePassXC
|
||||
* @param location Custom proxy location
|
||||
* @return script The JSON script file
|
||||
*/
|
||||
QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location)
|
||||
{
|
||||
QString path = getProxyPath(proxy, location);
|
||||
|
||||
QJsonObject script;
|
||||
script["name"] = HOST_NAME;
|
||||
script["description"] = QString("KeePassXC integration with native messaging support");
|
||||
script["path"] = path;
|
||||
script["type"] = QString("stdio");
|
||||
|
||||
QJsonArray arr;
|
||||
if (browser == SupportedBrowsers::FIREFOX || browser == SupportedBrowsers::TOR_BROWSER) {
|
||||
for (const QString& extension : ALLOWED_EXTENSIONS) {
|
||||
arr.append(extension);
|
||||
}
|
||||
script["allowed_extensions"] = arr;
|
||||
} else {
|
||||
for (const QString& origin : ALLOWED_ORIGINS) {
|
||||
arr.append(origin);
|
||||
}
|
||||
script["allowed_origins"] = arr;
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a registry setting is found with default value
|
||||
*
|
||||
* @param settings Registry path
|
||||
* @return bool Is the registry value found
|
||||
*/
|
||||
bool HostInstaller::registryEntryFound(const QSettings& settings)
|
||||
{
|
||||
return !settings.value("Default").isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a JSON script file
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @param script JSON native messaging script object
|
||||
* @return bool Write succeeds
|
||||
*/
|
||||
bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& script)
|
||||
{
|
||||
QString path = getPath(browser);
|
||||
QString installDir = getInstallDir(browser);
|
||||
QDir dir(installDir);
|
||||
if (!dir.exists()) {
|
||||
QDir().mkpath(installDir);
|
||||
}
|
||||
|
||||
QFile scriptFile(path);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc(script);
|
||||
return scriptFile.write(doc.toJson()) >= 0;
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef HOSTINSTALLER_H
|
||||
#define HOSTINSTALLER_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
|
||||
class HostInstaller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum SupportedBrowsers : int
|
||||
{
|
||||
CHROME = 0,
|
||||
CHROMIUM = 1,
|
||||
FIREFOX = 2,
|
||||
VIVALDI = 3,
|
||||
TOR_BROWSER = 4,
|
||||
BRAVE = 5,
|
||||
EDGE = 6
|
||||
};
|
||||
|
||||
public:
|
||||
HostInstaller();
|
||||
bool checkIfInstalled(SupportedBrowsers browser);
|
||||
bool checkIfProxyExists(const bool& proxy, const QString& location, QString& path) const;
|
||||
void installBrowser(SupportedBrowsers browser,
|
||||
const bool& enabled,
|
||||
const bool& proxy = false,
|
||||
const QString& location = "");
|
||||
void updateBinaryPaths(const bool& proxy, const QString& location = "");
|
||||
|
||||
private:
|
||||
QString getTargetPath(SupportedBrowsers browser) const;
|
||||
QString getBrowserName(SupportedBrowsers browser) const;
|
||||
QString getPath(SupportedBrowsers browser) const;
|
||||
QString getInstallDir(SupportedBrowsers browser) const;
|
||||
QString getProxyPath(const bool& proxy, const QString& location) const;
|
||||
QJsonObject constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location);
|
||||
bool registryEntryFound(const QSettings& settings);
|
||||
bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
|
||||
|
||||
private:
|
||||
const QString HOST_NAME;
|
||||
const QStringList ALLOWED_EXTENSIONS;
|
||||
const QStringList ALLOWED_ORIGINS;
|
||||
const QString TARGET_DIR_CHROME;
|
||||
const QString TARGET_DIR_CHROMIUM;
|
||||
const QString TARGET_DIR_FIREFOX;
|
||||
const QString TARGET_DIR_VIVALDI;
|
||||
const QString TARGET_DIR_TOR_BROWSER;
|
||||
const QString TARGET_DIR_BRAVE;
|
||||
const QString TARGET_DIR_EDGE;
|
||||
};
|
||||
|
||||
#endif // HOSTINSTALLER_H
|
313
src/browser/NativeMessageInstaller.cpp
Normal file
313
src/browser/NativeMessageInstaller.cpp
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessageInstaller.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
|
||||
using namespace BrowserShared;
|
||||
|
||||
namespace
|
||||
{
|
||||
const QString HOST_NAME = QStringLiteral("org.keepassxc.keepassxc_browser");
|
||||
const QStringList ALLOWED_EXTENSIONS = QStringList() << QStringLiteral("keepassxc-browser@keepassxc.org");
|
||||
const QStringList ALLOWED_ORIGINS = QStringList()
|
||||
<< QStringLiteral("chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/")
|
||||
<< QStringLiteral("chrome-extension://oboonakemofpalcgghocfoadofidjkkk/");
|
||||
#if defined(Q_OS_MACOS)
|
||||
const QString TARGET_DIR_CHROME = QStringLiteral("/Library/Application Support/Google/Chrome/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_CHROMIUM = QStringLiteral("/Library/Application Support/Chromium/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_FIREFOX = QStringLiteral("/Library/Application Support/Mozilla/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_VIVALDI = QStringLiteral("/Library/Application Support/Vivaldi/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_TOR_BROWSER =
|
||||
QStringLiteral("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_BRAVE =
|
||||
QStringLiteral("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_EDGE = QStringLiteral("/Library/Application Support/Microsoft Edge/NativeMessagingHosts");
|
||||
#elif defined(Q_OS_WIN)
|
||||
const QString TARGET_DIR_CHROME = QStringLiteral(
|
||||
"HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
|
||||
const QString TARGET_DIR_CHROMIUM =
|
||||
QStringLiteral("HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
|
||||
const QString TARGET_DIR_FIREFOX =
|
||||
QStringLiteral("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
|
||||
const QString TARGET_DIR_VIVALDI = TARGET_DIR_CHROME;
|
||||
const QString TARGET_DIR_TOR_BROWSER = TARGET_DIR_FIREFOX;
|
||||
const QString TARGET_DIR_BRAVE = TARGET_DIR_CHROME;
|
||||
const QString TARGET_DIR_EDGE = QStringLiteral(
|
||||
"HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser");
|
||||
#else
|
||||
const QString TARGET_DIR_CHROME = QStringLiteral("/google-chrome/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_CHROMIUM = QStringLiteral("/chromium/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_FIREFOX = QStringLiteral("/.mozilla/native-messaging-hosts");
|
||||
const QString TARGET_DIR_VIVALDI = QStringLiteral("/vivaldi/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_TOR_BROWSER = QStringLiteral(
|
||||
"/torbrowser/tbb/x86_64/tor-browser_en-US/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts");
|
||||
const QString TARGET_DIR_BRAVE = QStringLiteral("/BraveSoftware/Brave-Browser/NativeMessagingHosts");
|
||||
const QString TARGET_DIR_EDGE = QStringLiteral("/microsoftedge/NativeMessagingHosts");
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* Checks if the selected browser has native messaging host properly installed
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return bool Script is installed correctly
|
||||
*/
|
||||
bool NativeMessageInstaller::isBrowserEnabled(SupportedBrowsers browser)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
return !settings.value("Default").isNull();
|
||||
#else
|
||||
return QFile::exists(getNativeMessagePath(browser));
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs native messaging JSON script for the selected browser
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @param enabled Is browser integration enabled
|
||||
*/
|
||||
void NativeMessageInstaller::setBrowserEnabled(SupportedBrowsers browser, bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
#ifdef Q_OS_WIN
|
||||
// Create a registry key
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
settings.setValue("Default", getNativeMessagePath(browser));
|
||||
#endif
|
||||
// Always create the script file
|
||||
if (!createNativeMessageFile(browser)) {
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QObject::tr("Browser Plugin Failure"),
|
||||
QObject::tr("Could not save the native messaging script file for %1.").arg(getBrowserName(browser)),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
} else {
|
||||
// Remove the script file
|
||||
QString fileName = getNativeMessagePath(browser);
|
||||
QFile::remove(fileName);
|
||||
#ifdef Q_OS_WIN
|
||||
// Remove the registry entry
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
settings.remove("Default");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the paths to native messaging host for each browser that has been enabled
|
||||
*/
|
||||
void NativeMessageInstaller::updateBinaryPaths()
|
||||
{
|
||||
for (int i = 0; i < SupportedBrowsers::MAX_SUPPORTED; ++i) {
|
||||
if (isBrowserEnabled(static_cast<SupportedBrowsers>(i))) {
|
||||
setBrowserEnabled(static_cast<SupportedBrowsers>(i), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target path for each browser. Windows uses a registry path instead of a file path
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString Current target path for the selected browser
|
||||
*/
|
||||
QString NativeMessageInstaller::getTargetPath(SupportedBrowsers browser) const
|
||||
{
|
||||
switch (browser) {
|
||||
case SupportedBrowsers::CHROME:
|
||||
return TARGET_DIR_CHROME;
|
||||
case SupportedBrowsers::CHROMIUM:
|
||||
return TARGET_DIR_CHROMIUM;
|
||||
case SupportedBrowsers::FIREFOX:
|
||||
return TARGET_DIR_FIREFOX;
|
||||
case SupportedBrowsers::VIVALDI:
|
||||
return TARGET_DIR_VIVALDI;
|
||||
case SupportedBrowsers::TOR_BROWSER:
|
||||
return TARGET_DIR_TOR_BROWSER;
|
||||
case SupportedBrowsers::BRAVE:
|
||||
return TARGET_DIR_BRAVE;
|
||||
case SupportedBrowsers::EDGE:
|
||||
return TARGET_DIR_EDGE;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the browser name
|
||||
* Needed for Windows to separate Chromium- or Firefox-based scripts
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString Name of the selected browser
|
||||
*/
|
||||
QString NativeMessageInstaller::getBrowserName(SupportedBrowsers browser) const
|
||||
{
|
||||
switch (browser) {
|
||||
case SupportedBrowsers::CHROME:
|
||||
return QStringLiteral("chrome");
|
||||
case SupportedBrowsers::CHROMIUM:
|
||||
return QStringLiteral("chromium");
|
||||
case SupportedBrowsers::FIREFOX:
|
||||
return QStringLiteral("firefox");
|
||||
case SupportedBrowsers::VIVALDI:
|
||||
return QStringLiteral("vivaldi");
|
||||
case SupportedBrowsers::TOR_BROWSER:
|
||||
return QStringLiteral("tor-browser");
|
||||
case SupportedBrowsers::BRAVE:
|
||||
return QStringLiteral("brave");
|
||||
case SupportedBrowsers::EDGE:
|
||||
return QStringLiteral("edge");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of native messaging JSON script for the selected browser
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @return QString JSON script path for the selected browser
|
||||
*/
|
||||
QString NativeMessageInstaller::getNativeMessagePath(SupportedBrowsers browser) const
|
||||
{
|
||||
QString basePath;
|
||||
#if defined(Q_OS_WIN)
|
||||
// If portable settings file exists save the JSON scripts to the application folder
|
||||
if (QFile::exists(QCoreApplication::applicationDirPath() + QStringLiteral("/keepassxc.ini"))) {
|
||||
basePath = QCoreApplication::applicationDirPath();
|
||||
} else {
|
||||
basePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
}
|
||||
return QStringLiteral("%1/%2_%3.json").arg(basePath, HOST_NAME, getBrowserName(browser));
|
||||
#elif defined(Q_OS_LINUX)
|
||||
if (browser == SupportedBrowsers::TOR_BROWSER) {
|
||||
// Tor Browser launcher stores its config in ~/.local/share/...
|
||||
basePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
|
||||
} else if (browser == SupportedBrowsers::FIREFOX) {
|
||||
// Firefox stores its config in ~/
|
||||
basePath = QDir::homePath();
|
||||
} else {
|
||||
basePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
}
|
||||
#else
|
||||
basePath = QDir::homePath();
|
||||
#endif
|
||||
return QStringLiteral("%1%2/%3.json").arg(basePath, getTargetPath(browser), HOST_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to keepassxc-proxy binary
|
||||
*
|
||||
* @param location Custom proxy path
|
||||
* @return path Path to keepassxc-proxy
|
||||
*/
|
||||
QString NativeMessageInstaller::getProxyPath() const
|
||||
{
|
||||
if (browserSettings()->useCustomProxy()) {
|
||||
return browserSettings()->customProxyLocation();
|
||||
}
|
||||
|
||||
QString path;
|
||||
#ifdef KEEPASSXC_DIST_APPIMAGE
|
||||
path = QProcessEnvironment::systemEnvironment().value("APPIMAGE");
|
||||
#else
|
||||
path = QCoreApplication::applicationDirPath() + QStringLiteral("/keepassxc-proxy");
|
||||
#ifdef Q_OS_WIN
|
||||
path.append(QStringLiteral(".exe"));
|
||||
#endif // #ifdef Q_OS_WIN
|
||||
|
||||
#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
|
||||
return QDir::toNativeSeparators(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the JSON script file used with native messaging
|
||||
*
|
||||
* @param browser Browser (Chromium- and Firefox-based browsers need a different parameters for the script)
|
||||
* @param location Custom proxy location
|
||||
* @return script The JSON script file
|
||||
*/
|
||||
QJsonObject NativeMessageInstaller::constructFile(SupportedBrowsers browser)
|
||||
{
|
||||
QJsonObject script;
|
||||
script["name"] = HOST_NAME;
|
||||
script["description"] = QStringLiteral("KeePassXC integration with native messaging support");
|
||||
script["path"] = getProxyPath();
|
||||
script["type"] = QStringLiteral("stdio");
|
||||
|
||||
QJsonArray arr;
|
||||
if (browser == SupportedBrowsers::FIREFOX || browser == SupportedBrowsers::TOR_BROWSER) {
|
||||
for (const QString& extension : ALLOWED_EXTENSIONS) {
|
||||
arr.append(extension);
|
||||
}
|
||||
script["allowed_extensions"] = arr;
|
||||
} else {
|
||||
for (const QString& origin : ALLOWED_ORIGINS) {
|
||||
arr.append(origin);
|
||||
}
|
||||
script["allowed_origins"] = arr;
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a JSON script file
|
||||
*
|
||||
* @param browser Selected browser
|
||||
* @param script JSON native messaging script object
|
||||
* @return bool Write succeeds
|
||||
*/
|
||||
bool NativeMessageInstaller::createNativeMessageFile(SupportedBrowsers browser)
|
||||
{
|
||||
auto path = getNativeMessagePath(browser);
|
||||
|
||||
// Make the parent directory path if necessary
|
||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||
|
||||
QFile scriptFile(path);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
qWarning() << "Browser Plugin: Failed to open native message file for writing at " << scriptFile.fileName();
|
||||
qWarning() << scriptFile.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc(constructFile(browser));
|
||||
if (scriptFile.write(doc.toJson()) < 0) {
|
||||
qWarning() << "Browser Plugin: Failed to write native message file at " << scriptFile.fileName();
|
||||
qWarning() << scriptFile.errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
46
src/browser/NativeMessageInstaller.h
Normal file
46
src/browser/NativeMessageInstaller.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGEINSTALLER_H
|
||||
#define NATIVEMESSAGEINSTALLER_H
|
||||
|
||||
#include "BrowserShared.h"
|
||||
#include <QJsonObject>
|
||||
|
||||
class NativeMessageInstaller
|
||||
{
|
||||
public:
|
||||
NativeMessageInstaller() = default;
|
||||
|
||||
void setBrowserEnabled(BrowserShared::SupportedBrowsers browser, bool enabled);
|
||||
bool isBrowserEnabled(BrowserShared::SupportedBrowsers browser);
|
||||
|
||||
QString getProxyPath() const;
|
||||
void updateBinaryPaths();
|
||||
|
||||
private:
|
||||
QString getTargetPath(BrowserShared::SupportedBrowsers browser) const;
|
||||
QString getBrowserName(BrowserShared::SupportedBrowsers browser) const;
|
||||
QString getNativeMessagePath(BrowserShared::SupportedBrowsers browser) const;
|
||||
QJsonObject constructFile(BrowserShared::SupportedBrowsers browser);
|
||||
bool createNativeMessageFile(BrowserShared::SupportedBrowsers browser);
|
||||
|
||||
Q_DISABLE_COPY(NativeMessageInstaller);
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGEINSTALLER_H
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessagingBase.h"
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
NativeMessagingBase::NativeMessagingBase(const bool enabled)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
Q_UNUSED(enabled);
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#else
|
||||
if (enabled) {
|
||||
m_notifier.reset(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this));
|
||||
connect(m_notifier.data(), SIGNAL(activated(int)), this, SLOT(newNativeMessage()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingBase::newNativeMessage()
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
|
||||
struct kevent ev[1];
|
||||
struct timespec ts = {5, 0};
|
||||
|
||||
int fd = kqueue();
|
||||
if (fd == -1) {
|
||||
m_notifier->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
EV_SET(ev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, nullptr);
|
||||
if (kevent(fd, ev, 1, nullptr, 0, &ts) == -1) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = kevent(fd, NULL, 0, ev, 1, &ts);
|
||||
if (ret < 1) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
int fd = epoll_create(5);
|
||||
struct epoll_event event;
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = 0;
|
||||
if (epoll_ctl(fd, EPOLL_CTL_ADD, 0, &event) != 0) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (epoll_wait(fd, &event, 1, 5000) < 1) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
readLength();
|
||||
#ifndef Q_OS_WIN
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingBase::readNativeMessages()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
quint32 length = 0;
|
||||
while (m_running.load() != 0 && !std::cin.eof()) {
|
||||
length = 0;
|
||||
std::cin.readsome(reinterpret_cast<char*>(&length), 4);
|
||||
readStdIn(length);
|
||||
QThread::msleep(100);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NativeMessagingBase::jsonToString(const QJsonObject& json) const
|
||||
{
|
||||
return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
void NativeMessagingBase::sendReply(const QJsonObject& json)
|
||||
{
|
||||
if (!json.isEmpty()) {
|
||||
sendReply(jsonToString(json));
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingBase::sendReply(const QString& reply)
|
||||
{
|
||||
if (!reply.isEmpty()) {
|
||||
QByteArray bytes = reply.toUtf8();
|
||||
uint len = bytes.size();
|
||||
std::cout << char(((len >> 0) & 0xFF)) << char(((len >> 8) & 0xFF)) << char(((len >> 16) & 0xFF))
|
||||
<< char(((len >> 24) & 0xFF));
|
||||
std::cout << reply.toStdString() << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
QString NativeMessagingBase::getLocalServerPath() const
|
||||
{
|
||||
const QString serverPath = "/kpxc_server";
|
||||
#if defined(KEEPASSXC_DIST_SNAP)
|
||||
return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverPath;
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
// Use XDG_RUNTIME_DIR instead of /tmp if it's available
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||
return path.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverPath
|
||||
: path + serverPath;
|
||||
#else // Q_OS_MACOS, Q_OS_WIN and others
|
||||
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverPath;
|
||||
#endif
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGINGBASE_H
|
||||
#define NATIVEMESSAGINGBASE_H
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QFuture>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
static const int NATIVE_MSG_MAX_LENGTH = 1024 * 1024;
|
||||
|
||||
class NativeMessagingBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NativeMessagingBase(const bool enabled);
|
||||
~NativeMessagingBase() = default;
|
||||
|
||||
protected slots:
|
||||
void newNativeMessage();
|
||||
|
||||
protected:
|
||||
virtual void readLength() = 0;
|
||||
virtual bool readStdIn(const quint32 length) = 0;
|
||||
virtual void readNativeMessages();
|
||||
QString jsonToString(const QJsonObject& json) const;
|
||||
void sendReply(const QJsonObject& json);
|
||||
void sendReply(const QString& reply);
|
||||
QString getLocalServerPath() const;
|
||||
|
||||
protected:
|
||||
QAtomicInt m_running;
|
||||
QSharedPointer<QSocketNotifier> m_notifier;
|
||||
QFuture<void> m_future;
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGBASE_H
|
@ -1,222 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessagingHost.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "sodium.h"
|
||||
#include <QMutexLocker>
|
||||
#include <QtNetwork>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Winsock2.h>
|
||||
#endif
|
||||
|
||||
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent, const bool enabled)
|
||||
: NativeMessagingBase(enabled)
|
||||
, m_mutex(QMutex::Recursive)
|
||||
, m_browserService(parent)
|
||||
, m_browserClients(m_browserService)
|
||||
{
|
||||
m_localServer.reset(new QLocalServer(this));
|
||||
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
m_running.store(0);
|
||||
|
||||
if (browserSettings()->isEnabled() && m_running.load() == 0) {
|
||||
run();
|
||||
}
|
||||
|
||||
connect(&m_browserService, SIGNAL(databaseLocked()), this, SLOT(databaseLocked()));
|
||||
connect(&m_browserService, SIGNAL(databaseUnlocked()), this, SLOT(databaseUnlocked()));
|
||||
}
|
||||
|
||||
NativeMessagingHost::~NativeMessagingHost()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
int NativeMessagingHost::init()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return sodium_init();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::run()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_running.load() == 0 && init() == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
|
||||
if (browserSettings()->updateBinaryPath()) {
|
||||
browserSettings()->updateBinaryPaths(
|
||||
browserSettings()->useCustomProxy() ? browserSettings()->customProxyLocation() : "");
|
||||
}
|
||||
|
||||
m_running.store(1);
|
||||
#ifdef Q_OS_WIN
|
||||
m_future =
|
||||
QtConcurrent::run(this, static_cast<void (NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
|
||||
#endif
|
||||
|
||||
if (browserSettings()->supportBrowserProxy()) {
|
||||
QString serverPath = getLocalServerPath();
|
||||
QFile::remove(serverPath);
|
||||
|
||||
// Ensure that STDIN is not being listened when proxy is used
|
||||
if (m_notifier && m_notifier->isEnabled()) {
|
||||
m_notifier->setEnabled(false);
|
||||
}
|
||||
|
||||
if (m_localServer->isListening()) {
|
||||
m_localServer->close();
|
||||
}
|
||||
|
||||
m_localServer->listen(serverPath);
|
||||
connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(newLocalConnection()));
|
||||
} else {
|
||||
m_localServer->close();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::stop()
|
||||
{
|
||||
databaseLocked();
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_socketList.clear();
|
||||
m_running.testAndSetOrdered(1, 0);
|
||||
m_future.waitForFinished();
|
||||
m_localServer->close();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readLength()
|
||||
{
|
||||
quint32 length = 0;
|
||||
std::cin.read(reinterpret_cast<char*>(&length), 4);
|
||||
if (!std::cin.eof() && length > 0) {
|
||||
readStdIn(length);
|
||||
} else {
|
||||
m_notifier->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool NativeMessagingHost::readStdIn(const quint32 length)
|
||||
{
|
||||
if (length <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray arr;
|
||||
arr.reserve(length);
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
for (quint32 i = 0; i < length; ++i) {
|
||||
int c = std::getchar();
|
||||
if (c == EOF) {
|
||||
// message ended prematurely, ignore it and return
|
||||
return false;
|
||||
}
|
||||
arr.append(static_cast<char>(c));
|
||||
}
|
||||
|
||||
if (arr.length() > 0) {
|
||||
sendReply(m_browserClients.readResponse(arr));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeMessagingHost::newLocalConnection()
|
||||
{
|
||||
QLocalSocket* socket = m_localServer->nextPendingConnection();
|
||||
if (socket) {
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(disconnectSocket()));
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::newLocalMessage()
|
||||
{
|
||||
QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
|
||||
if (!socket || socket->bytesAvailable() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
|
||||
int socketDesc = socket->socketDescriptor();
|
||||
if (socketDesc) {
|
||||
int max = NATIVE_MSG_MAX_LENGTH;
|
||||
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
|
||||
}
|
||||
|
||||
QByteArray arr = socket->readAll();
|
||||
if (arr.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_socketList.contains(socket)) {
|
||||
m_socketList.push_back(socket);
|
||||
}
|
||||
|
||||
QString reply = jsonToString(m_browserClients.readResponse(arr));
|
||||
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
|
||||
QByteArray arr = reply.toUtf8();
|
||||
socket->write(arr.constData(), arr.length());
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::sendReplyToAllClients(const QJsonObject& json)
|
||||
{
|
||||
QString reply = jsonToString(json);
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (const auto socket : m_socketList) {
|
||||
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
|
||||
QByteArray arr = reply.toUtf8();
|
||||
socket->write(arr.constData(), arr.length());
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::disconnectSocket()
|
||||
{
|
||||
QLocalSocket* socket(qobject_cast<QLocalSocket*>(QObject::sender()));
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (auto s : m_socketList) {
|
||||
if (s == socket) {
|
||||
m_socketList.removeOne(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::databaseLocked()
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = QString("database-locked");
|
||||
sendReplyToAllClients(response);
|
||||
}
|
||||
|
||||
void NativeMessagingHost::databaseUnlocked()
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = QString("database-unlocked");
|
||||
sendReplyToAllClients(response);
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGINGHOST_H
|
||||
#define NATIVEMESSAGINGHOST_H
|
||||
|
||||
#include "BrowserClients.h"
|
||||
#include "BrowserService.h"
|
||||
#include "NativeMessagingBase.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
|
||||
class NativeMessagingHost : public NativeMessagingBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
typedef QList<QLocalSocket*> SocketList;
|
||||
|
||||
public:
|
||||
explicit NativeMessagingHost(DatabaseTabWidget* parent = nullptr, const bool enabled = false);
|
||||
~NativeMessagingHost() override;
|
||||
int init();
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void quit();
|
||||
|
||||
private:
|
||||
void readLength() override;
|
||||
bool readStdIn(const quint32 length) override;
|
||||
void sendReplyToAllClients(const QJsonObject& json);
|
||||
|
||||
private slots:
|
||||
void databaseLocked();
|
||||
void databaseUnlocked();
|
||||
void newLocalConnection();
|
||||
void newLocalMessage();
|
||||
void disconnectSocket();
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
BrowserService m_browserService;
|
||||
BrowserClients m_browserClients;
|
||||
QSharedPointer<QLocalServer> m_localServer;
|
||||
SocketList m_socketList;
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGHOST_H
|
@ -44,6 +44,9 @@
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "gui/osutils/macutils/MacUtils.h"
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
#include "touchid/TouchID.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_UPDATECHECK
|
||||
@ -56,7 +59,7 @@
|
||||
#include "sshagent/AgentSettingsPage.h"
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
#include "keeshare/KeeShare.h"
|
||||
#include "keeshare/SettingsPageKeeShare.h"
|
||||
#endif
|
||||
@ -66,9 +69,8 @@
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#include "browser/BrowserOptionDialog.h"
|
||||
#include "browser/BrowserSettings.h"
|
||||
#include "browser/NativeMessagingHost.h"
|
||||
#include "browser/BrowserService.h"
|
||||
#include "browser/BrowserSettingsPage.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
|
||||
@ -77,61 +79,6 @@
|
||||
#include <QtDBus/QtDBus>
|
||||
#endif
|
||||
|
||||
#include "gui/ApplicationSettingsWidget.h"
|
||||
#include "gui/PasswordGeneratorWidget.h"
|
||||
|
||||
#include "touchid/TouchID.h"
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
class BrowserPlugin : public ISettingsPage
|
||||
{
|
||||
public:
|
||||
explicit BrowserPlugin(DatabaseTabWidget* tabWidget)
|
||||
{
|
||||
m_nativeMessagingHost =
|
||||
QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, browserSettings()->isEnabled()));
|
||||
}
|
||||
|
||||
~BrowserPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
QString name() override
|
||||
{
|
||||
return QObject::tr("Browser Integration");
|
||||
}
|
||||
|
||||
QIcon icon() override
|
||||
{
|
||||
return Resources::instance()->icon("internet-web-browser");
|
||||
}
|
||||
|
||||
QWidget* createWidget() override
|
||||
{
|
||||
BrowserOptionDialog* dlg = new BrowserOptionDialog();
|
||||
return dlg;
|
||||
}
|
||||
|
||||
void loadSettings(QWidget* widget) override
|
||||
{
|
||||
qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
|
||||
}
|
||||
|
||||
void saveSettings(QWidget* widget) override
|
||||
{
|
||||
qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
|
||||
if (browserSettings()->isEnabled()) {
|
||||
m_nativeMessagingHost->run();
|
||||
} else {
|
||||
m_nativeMessagingHost->stop();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<NativeMessagingHost> m_nativeMessagingHost;
|
||||
};
|
||||
#endif
|
||||
|
||||
const QString MainWindow::BaseWindowTitle = "KeePassXC";
|
||||
|
||||
MainWindow* g_MainWindow = nullptr;
|
||||
@ -186,13 +133,19 @@ MainWindow::MainWindow()
|
||||
restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray());
|
||||
restoreState(config()->get(Config::GUI_MainWindowState).toByteArray());
|
||||
#ifdef WITH_XC_BROWSER
|
||||
m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
|
||||
m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
|
||||
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, browserService(), &BrowserService::databaseLocked);
|
||||
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, browserService(), &BrowserService::databaseUnlocked);
|
||||
connect(m_ui->tabWidget,
|
||||
&DatabaseTabWidget::activateDatabaseChanged,
|
||||
browserService(),
|
||||
&BrowserService::activeDatabaseChanged);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
connect(sshAgent(), SIGNAL(error(QString)), this, SLOT(showErrorMessage(QString)));
|
||||
connect(sshAgent(), SIGNAL(enabledChanged(bool)), this, SLOT(agentEnabled(bool)));
|
||||
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
|
||||
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage());
|
||||
|
||||
m_entryContextMenu->addSeparator();
|
||||
m_entryContextMenu->addAction(m_ui->actionEntryAddToAgent);
|
||||
@ -565,6 +518,15 @@ MainWindow::~MainWindow()
|
||||
{
|
||||
}
|
||||
|
||||
QList<DatabaseWidget*> MainWindow::getOpenDatabases()
|
||||
{
|
||||
QList<DatabaseWidget*> dbWidgets;
|
||||
for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
|
||||
dbWidgets << m_ui->tabWidget->databaseWidgetFromIndex(i);
|
||||
}
|
||||
return dbWidgets;
|
||||
}
|
||||
|
||||
void MainWindow::showErrorMessage(const QString& message)
|
||||
{
|
||||
m_ui->globalMessageWidget->showMessage(message, MessageWidget::Error);
|
||||
|
@ -48,6 +48,8 @@ public:
|
||||
MainWindow();
|
||||
~MainWindow();
|
||||
|
||||
QList<DatabaseWidget*> getOpenDatabases();
|
||||
|
||||
enum StackedWidgetIndex
|
||||
{
|
||||
DatabaseTabScreen = 0,
|
||||
|
@ -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()
|
||||
|
@ -77,7 +77,6 @@ protected:
|
||||
private:
|
||||
QPointer<CustomData> m_customData;
|
||||
QPointer<QStandardItemModel> m_customDataModel;
|
||||
BrowserService m_browserService;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSWIDGETBROWSER_H
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -15,19 +14,17 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
include_directories(${BROWSER_SOURCE_DIR})
|
||||
|
||||
set(proxy_SOURCES
|
||||
../core/Alloc.cpp
|
||||
../browser/BrowserShared.cpp
|
||||
keepassxc-proxy.cpp
|
||||
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
|
||||
NativeMessagingHost.cpp)
|
||||
NativeMessagingProxy.cpp)
|
||||
|
||||
add_library(proxy STATIC ${proxy_SOURCES})
|
||||
target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
|
||||
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
|
||||
target_link_libraries(keepassxc-proxy proxy)
|
||||
# Alloc must be defined in a static library to prevent clashing with clang ASAN definitions
|
||||
add_library(proxy_alloc STATIC ../core/Alloc.cpp)
|
||||
target_link_libraries(proxy_alloc PRIVATE Qt5::Core ${sodium_LIBRARY_RELEASE})
|
||||
|
||||
add_executable(keepassxc-proxy ${proxy_SOURCES})
|
||||
target_link_libraries(keepassxc-proxy proxy_alloc Qt5::Core Qt5::Network)
|
||||
install(TARGETS keepassxc-proxy
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${PROXY_INSTALL_DIR} COMPONENT Runtime)
|
||||
@ -56,7 +53,4 @@ if(WITH_XC_BROWSER)
|
||||
COMMAND ${CMAKE_COMMAND} -E copy keepassxc-proxy ${PROXY_APP_DIR}/keepassxc-proxy
|
||||
COMMENT "Copying keepassxc-proxy inside the application")
|
||||
endif()
|
||||
if(MINGW)
|
||||
target_link_libraries(keepassxc-proxy Wtsapi32.lib Ws2_32.lib)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessagingHost.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
NativeMessagingHost::NativeMessagingHost()
|
||||
: NativeMessagingBase(true)
|
||||
{
|
||||
m_localSocket = new QLocalSocket();
|
||||
m_localSocket->connectToServer(getLocalServerPath());
|
||||
m_localSocket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
|
||||
|
||||
int socketDesc = m_localSocket->socketDescriptor();
|
||||
if (socketDesc) {
|
||||
int max = NATIVE_MSG_MAX_LENGTH;
|
||||
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
m_running.store(1);
|
||||
m_future = QtConcurrent::run(this, &NativeMessagingHost::readNativeMessages);
|
||||
#endif
|
||||
connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
|
||||
connect(m_localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket()));
|
||||
connect(m_localSocket,
|
||||
SIGNAL(stateChanged(QLocalSocket::LocalSocketState)),
|
||||
SLOT(socketStateChanged(QLocalSocket::LocalSocketState)));
|
||||
}
|
||||
|
||||
NativeMessagingHost::~NativeMessagingHost()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
m_future.waitForFinished();
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readNativeMessages()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
quint32 length = 0;
|
||||
while (m_running.load() == 1 && !std::cin.eof()) {
|
||||
length = 0;
|
||||
std::cin.read(reinterpret_cast<char*>(&length), 4);
|
||||
if (!readStdIn(length)) {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
QThread::msleep(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readLength()
|
||||
{
|
||||
quint32 length = 0;
|
||||
std::cin.read(reinterpret_cast<char*>(&length), 4);
|
||||
if (!std::cin.eof() && length > 0) {
|
||||
readStdIn(length);
|
||||
} else {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
}
|
||||
|
||||
bool NativeMessagingHost::readStdIn(const quint32 length)
|
||||
{
|
||||
if (length <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray arr;
|
||||
arr.reserve(length);
|
||||
|
||||
for (quint32 i = 0; i < length; ++i) {
|
||||
int c = std::getchar();
|
||||
if (c == EOF) {
|
||||
// message ended prematurely, ignore it and return
|
||||
return false;
|
||||
}
|
||||
arr.append(static_cast<char>(c));
|
||||
}
|
||||
|
||||
if (arr.length() > 0 && m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
|
||||
m_localSocket->write(arr.constData(), arr.length());
|
||||
m_localSocket->flush();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeMessagingHost::newLocalMessage()
|
||||
{
|
||||
if (!m_localSocket || m_localSocket->bytesAvailable() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray arr = m_localSocket->readAll();
|
||||
if (!arr.isEmpty()) {
|
||||
sendReply(arr);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::deleteSocket()
|
||||
{
|
||||
if (m_notifier) {
|
||||
m_notifier->setEnabled(false);
|
||||
}
|
||||
m_localSocket->deleteLater();
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::socketStateChanged(QLocalSocket::LocalSocketState socketState)
|
||||
{
|
||||
if (socketState == QLocalSocket::UnconnectedState || socketState == QLocalSocket::ClosingState) {
|
||||
m_running.testAndSetOrdered(1, 0);
|
||||
}
|
||||
}
|
110
src/proxy/NativeMessagingProxy.cpp
Normal file
110
src/proxy/NativeMessagingProxy.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessagingProxy.h"
|
||||
#include "browser/BrowserShared.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
NativeMessagingProxy::NativeMessagingProxy()
|
||||
: QObject()
|
||||
{
|
||||
connect(this,
|
||||
&NativeMessagingProxy::stdinMessage,
|
||||
this,
|
||||
&NativeMessagingProxy::transferStdinMessage,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
setupStandardInput();
|
||||
setupLocalSocket();
|
||||
}
|
||||
|
||||
void NativeMessagingProxy::setupStandardInput()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
setmode(fileno(stdin), _O_BINARY);
|
||||
setmode(fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
|
||||
QtConcurrent::run([this] {
|
||||
while (std::cin.good()) {
|
||||
if (std::cin.peek() != EOF) {
|
||||
uint length = 0;
|
||||
for (uint i = 0; i < sizeof(uint); ++i) {
|
||||
length |= getchar() << (i * 8);
|
||||
}
|
||||
|
||||
QString msg;
|
||||
msg.reserve(length);
|
||||
for (uint i = 0; i < length; ++i) {
|
||||
msg.append(getchar());
|
||||
}
|
||||
|
||||
if (msg.length() > 0) {
|
||||
emit stdinMessage(msg);
|
||||
}
|
||||
}
|
||||
QThread::msleep(100);
|
||||
}
|
||||
QCoreApplication::quit();
|
||||
});
|
||||
}
|
||||
|
||||
void NativeMessagingProxy::transferStdinMessage(const QString& msg)
|
||||
{
|
||||
if (m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
|
||||
m_localSocket->write(msg.toUtf8(), msg.length());
|
||||
m_localSocket->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingProxy::setupLocalSocket()
|
||||
{
|
||||
m_localSocket.reset(new QLocalSocket());
|
||||
m_localSocket->connectToServer(BrowserShared::localServerPath());
|
||||
m_localSocket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
connect(m_localSocket.data(), SIGNAL(readyRead()), this, SLOT(transferSocketMessage()));
|
||||
connect(m_localSocket.data(), SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
|
||||
}
|
||||
|
||||
void NativeMessagingProxy::transferSocketMessage()
|
||||
{
|
||||
auto msg = m_localSocket->readAll();
|
||||
if (!msg.isEmpty()) {
|
||||
// Explicitly write the message length as 1 byte chunks
|
||||
uint len = msg.size();
|
||||
std::cout.write(reinterpret_cast<char*>(&len), sizeof(len));
|
||||
|
||||
// Write the message and flush the stream
|
||||
std::cout << msg.toStdString() << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingProxy::socketDisconnected()
|
||||
{
|
||||
// Shutdown the proxy when disconnected from the application
|
||||
QCoreApplication::quit();
|
||||
}
|
53
src/proxy/NativeMessagingProxy.h
Normal file
53
src/proxy/NativeMessagingProxy.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGINGPROXY_H
|
||||
#define NATIVEMESSAGINGPROXY_H
|
||||
|
||||
#include <QLocalSocket>
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class QWinEventNotifier;
|
||||
class QSocketNotifier;
|
||||
|
||||
class NativeMessagingProxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NativeMessagingProxy();
|
||||
~NativeMessagingProxy() override = default;
|
||||
|
||||
signals:
|
||||
void stdinMessage(QString msg);
|
||||
|
||||
public slots:
|
||||
void transferSocketMessage();
|
||||
void transferStdinMessage(const QString& msg);
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
void setupStandardInput();
|
||||
void setupLocalSocket();
|
||||
|
||||
private:
|
||||
QScopedPointer<QLocalSocket> m_localSocket;
|
||||
|
||||
Q_DISABLE_COPY(NativeMessagingProxy)
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGPROXY_H
|
@ -16,8 +16,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessagingHost.h"
|
||||
#include "NativeMessagingProxy.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
@ -79,6 +80,6 @@ int main(int argc, char* argv[])
|
||||
#else
|
||||
SetConsoleCtrlHandler(static_cast<PHANDLER_ROUTINE>(ConsoleHandler), TRUE);
|
||||
#endif
|
||||
NativeMessagingHost host;
|
||||
NativeMessagingProxy proxy;
|
||||
return a.exec();
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -16,11 +16,13 @@
|
||||
*/
|
||||
|
||||
#include "TestBrowser.h"
|
||||
|
||||
#include "TestGlobal.h"
|
||||
#include "browser/BrowserSettings.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "sodium/crypto_box.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
QTEST_GUILESS_MAIN(TestBrowser)
|
||||
@ -35,12 +37,12 @@ const QString CLIENTID = "testClient";
|
||||
void TestBrowser::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
m_browserService.reset(new BrowserService(nullptr));
|
||||
m_browserAction.reset(new BrowserAction(*m_browserService.data()));
|
||||
m_browserService = browserService();
|
||||
}
|
||||
|
||||
void TestBrowser::cleanupTestCase()
|
||||
void TestBrowser::init()
|
||||
{
|
||||
m_browserAction.reset(new BrowserAction());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,7 +56,7 @@ void TestBrowser::testChangePublicKeys()
|
||||
json["publicKey"] = PUBLICKEY;
|
||||
json["nonce"] = NONCE;
|
||||
|
||||
auto response = m_browserAction->handleAction(json);
|
||||
auto response = m_browserAction->processClientMessage(json);
|
||||
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
||||
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
||||
QCOMPARE(response["success"].toString(), TRUE_STR);
|
||||
@ -393,62 +395,6 @@ void TestBrowser::testSortEntries()
|
||||
QCOMPARE(result[3]->url(), QString("github.com/login"));
|
||||
}
|
||||
|
||||
void TestBrowser::testGetDatabaseGroups()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
auto* root = db->rootGroup();
|
||||
|
||||
QScopedPointer<Group> group1(new Group());
|
||||
group1->setParent(root);
|
||||
group1->setName("group1");
|
||||
|
||||
QScopedPointer<Group> group2(new Group());
|
||||
group2->setParent(root);
|
||||
group2->setName("group2");
|
||||
|
||||
QScopedPointer<Group> group3(new Group());
|
||||
group3->setParent(root);
|
||||
group3->setName("group3");
|
||||
|
||||
QScopedPointer<Group> group2_1(new Group());
|
||||
group2_1->setParent(group2.data());
|
||||
group2_1->setName("group2_1");
|
||||
|
||||
QScopedPointer<Group> group2_2(new Group());
|
||||
group2_2->setParent(group2.data());
|
||||
group2_2->setName("group2_2");
|
||||
|
||||
QScopedPointer<Group> group2_1_1(new Group());
|
||||
group2_1_1->setParent(group2_1.data());
|
||||
group2_1_1->setName("group2_1_1");
|
||||
|
||||
auto result = m_browserService->getDatabaseGroups(db);
|
||||
QCOMPARE(result.length(), 1);
|
||||
|
||||
auto groups = result["groups"].toArray();
|
||||
auto first = groups.at(0);
|
||||
auto children = first.toObject()["children"].toArray();
|
||||
QCOMPARE(first.toObject()["name"].toString(), QString("Root"));
|
||||
QCOMPARE(children.size(), 3);
|
||||
|
||||
auto firstChild = children.at(0);
|
||||
auto secondChild = children.at(1);
|
||||
auto thirdChild = children.at(2);
|
||||
QCOMPARE(firstChild.toObject()["name"].toString(), QString("group1"));
|
||||
QCOMPARE(secondChild.toObject()["name"].toString(), QString("group2"));
|
||||
QCOMPARE(thirdChild.toObject()["name"].toString(), QString("group3"));
|
||||
|
||||
auto childrenOfSecond = secondChild.toObject()["children"].toArray();
|
||||
auto firstOfCOS = childrenOfSecond.at(0);
|
||||
auto secondOfCOS = childrenOfSecond.at(1);
|
||||
QCOMPARE(firstOfCOS.toObject()["name"].toString(), QString("group2_1"));
|
||||
QCOMPARE(secondOfCOS.toObject()["name"].toString(), QString("group2_2"));
|
||||
|
||||
auto lastChildren = firstOfCOS.toObject()["children"].toArray();
|
||||
auto lastChild = lastChildren.at(0);
|
||||
QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1"));
|
||||
}
|
||||
|
||||
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
|
@ -30,7 +30,7 @@ class TestBrowser : public QObject
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void init();
|
||||
|
||||
void testChangePublicKeys();
|
||||
void testEncryptMessage();
|
||||
@ -46,14 +46,13 @@ private slots:
|
||||
void testInvalidEntries();
|
||||
void testSubdomainsAndPaths();
|
||||
void testSortEntries();
|
||||
void testGetDatabaseGroups();
|
||||
void testValidURLs();
|
||||
|
||||
private:
|
||||
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
||||
|
||||
QScopedPointer<BrowserAction> m_browserAction;
|
||||
QScopedPointer<BrowserService> m_browserService;
|
||||
QPointer<BrowserService> m_browserService;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_TESTBROWSER_H
|
||||
|
Binary file not shown.
@ -31,6 +31,7 @@
|
||||
#include <QTableView>
|
||||
#include <QToolBar>
|
||||
|
||||
#include "browser/BrowserService.h"
|
||||
#include "config-keepassx-tests.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Config.h"
|
||||
@ -82,30 +83,19 @@ void TestGuiBrowser::initTestCase()
|
||||
Bootstrap::restoreMainWindowState(*m_mainWindow);
|
||||
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
|
||||
m_mainWindow->show();
|
||||
|
||||
// Load the NewDatabase.kdbx file into temporary storage
|
||||
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabaseBrowser.kdbx"));
|
||||
QVERIFY(sourceDbFile.open(QIODevice::ReadOnly));
|
||||
QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
|
||||
sourceDbFile.close();
|
||||
}
|
||||
|
||||
// Every test starts with opening the temp database
|
||||
void TestGuiBrowser::init()
|
||||
{
|
||||
m_dbFile.reset(new TemporaryFile());
|
||||
// Write the temp storage to a temp database file for use in our tests
|
||||
QVERIFY(m_dbFile->open());
|
||||
QCOMPARE(m_dbFile->write(m_dbData), static_cast<qint64>((m_dbData.size())));
|
||||
m_dbFileName = QFileInfo(m_dbFile->fileName()).fileName();
|
||||
m_dbFilePath = m_dbFile->fileName();
|
||||
m_dbFile->close();
|
||||
m_dbFile->copyFromFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabaseBrowser.kdbx"));
|
||||
|
||||
// make sure window is activated or focus tests may fail
|
||||
m_mainWindow->activateWindow();
|
||||
QApplication::processEvents();
|
||||
|
||||
fileDialog()->setNextFileName(m_dbFilePath);
|
||||
fileDialog()->setNextFileName(m_dbFile->fileName());
|
||||
triggerAction("actionDatabaseOpen");
|
||||
|
||||
auto* databaseOpenWidget = m_tabWidget->currentDatabaseWidget()->findChild<QWidget*>("databaseOpenWidget");
|
||||
@ -241,6 +231,28 @@ void TestGuiBrowser::testAdditionalURLs()
|
||||
}
|
||||
}
|
||||
|
||||
void TestGuiBrowser::testGetDatabaseGroups()
|
||||
{
|
||||
auto result = browserService()->getDatabaseGroups();
|
||||
QCOMPARE(result.length(), 1);
|
||||
|
||||
auto groups = result["groups"].toArray();
|
||||
auto first = groups.at(0);
|
||||
auto children = first.toObject()["children"].toArray();
|
||||
QCOMPARE(first.toObject()["name"].toString(), QString("NewDatabase"));
|
||||
QCOMPARE(children.size(), 6);
|
||||
|
||||
auto firstChild = children.at(0).toObject();
|
||||
auto secondChild = children.at(1).toObject();
|
||||
QCOMPARE(firstChild["name"].toString(), QString("General"));
|
||||
QCOMPARE(secondChild["name"].toString(), QString("Windows"));
|
||||
|
||||
auto subGroups = firstChild["children"].toArray();
|
||||
QCOMPARE(subGroups.count(), 1);
|
||||
auto subGroupObj = subGroups.at(0).toObject();
|
||||
QCOMPARE(subGroupObj["name"].toString(), QString("SubGroup"));
|
||||
}
|
||||
|
||||
void TestGuiBrowser::triggerAction(const QString& name)
|
||||
{
|
||||
auto* action = m_mainWindow->findChild<QAction*>(name);
|
||||
|
@ -45,6 +45,7 @@ private slots:
|
||||
|
||||
void testEntrySettings();
|
||||
void testAdditionalURLs();
|
||||
void testGetDatabaseGroups();
|
||||
|
||||
private:
|
||||
void triggerAction(const QString& name);
|
||||
@ -57,10 +58,7 @@ private:
|
||||
QPointer<DatabaseTabWidget> m_tabWidget;
|
||||
QPointer<DatabaseWidget> m_dbWidget;
|
||||
QSharedPointer<Database> m_db;
|
||||
QByteArray m_dbData;
|
||||
QScopedPointer<TemporaryFile> m_dbFile;
|
||||
QString m_dbFileName;
|
||||
QString m_dbFilePath;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_TESTGUIBROWSER_H
|
||||
|
Loading…
Reference in New Issue
Block a user