mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-13 08:19:50 -05:00
Support for triggering Global Auto-Type from browser extension
This commit is contained in:
parent
be6835e42f
commit
c7cdce6e33
@ -164,11 +164,14 @@ void AutoType::loadPlugin(const QString& pluginPath)
|
|||||||
if (m_plugin) {
|
if (m_plugin) {
|
||||||
if (m_plugin->isAvailable()) {
|
if (m_plugin->isAvailable()) {
|
||||||
m_executor = m_plugin->createExecutor();
|
m_executor = m_plugin->createExecutor();
|
||||||
connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](const QString& name) {
|
connect(osUtils,
|
||||||
if (name == "autotype") {
|
&OSUtilsBase::globalShortcutTriggered,
|
||||||
startGlobalAutoType();
|
this,
|
||||||
}
|
[this](const QString& name, const QString& initialSearch) {
|
||||||
});
|
if (name == "autotype") {
|
||||||
|
startGlobalAutoType(initialSearch);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
unloadPlugin();
|
unloadPlugin();
|
||||||
}
|
}
|
||||||
@ -359,7 +362,7 @@ void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& se
|
|||||||
executeAutoTypeActions(entry, hideWindow, sequence);
|
executeAutoTypeActions(entry, hideWindow, sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoType::startGlobalAutoType()
|
void AutoType::startGlobalAutoType(const QString& search)
|
||||||
{
|
{
|
||||||
// Never Auto-Type into KeePassXC itself
|
// Never Auto-Type into KeePassXC itself
|
||||||
if (qApp->focusWindow()) {
|
if (qApp->focusWindow()) {
|
||||||
@ -382,6 +385,7 @@ void AutoType::startGlobalAutoType()
|
|||||||
tr("KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global "
|
tr("KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global "
|
||||||
"Auto-Type. Screen Recording is necessary to use the window title to find entries. If you "
|
"Auto-Type. Screen Recording is necessary to use the window title to find entries. If you "
|
||||||
"already granted permission, you may have to restart KeePassXC."));
|
"already granted permission, you may have to restart KeePassXC."));
|
||||||
|
qDebug() << "Oh noes macOS.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -397,14 +401,14 @@ void AutoType::startGlobalAutoType()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
emit globalAutoTypeTriggered();
|
emit globalAutoTypeTriggered(search);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global Autotype entry-point function
|
* Global Autotype entry-point function
|
||||||
* Perform global Auto-Type on the active window
|
* Perform global Auto-Type on the active window
|
||||||
*/
|
*/
|
||||||
void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList)
|
void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList, const QString& search)
|
||||||
{
|
{
|
||||||
if (!m_plugin) {
|
if (!m_plugin) {
|
||||||
return;
|
return;
|
||||||
@ -449,6 +453,10 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
|||||||
auto* selectDialog = new AutoTypeSelectDialog();
|
auto* selectDialog = new AutoTypeSelectDialog();
|
||||||
selectDialog->setMatches(matchList, dbList);
|
selectDialog->setMatches(matchList, dbList);
|
||||||
|
|
||||||
|
if (!search.isEmpty()) {
|
||||||
|
selectDialog->setSearchString(search);
|
||||||
|
}
|
||||||
|
|
||||||
connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
|
connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
|
||||||
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) {
|
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) {
|
||||||
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal);
|
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal);
|
||||||
|
@ -52,16 +52,16 @@ public:
|
|||||||
static void createTestInstance();
|
static void createTestInstance();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList);
|
void performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList, const QString& search = {});
|
||||||
void raiseWindow();
|
void raiseWindow();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void globalAutoTypeTriggered();
|
void globalAutoTypeTriggered(const QString& search);
|
||||||
void autotypePerformed();
|
void autotypePerformed();
|
||||||
void autotypeRejected();
|
void autotypeRejected();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startGlobalAutoType();
|
void startGlobalAutoType(const QString& search);
|
||||||
void unloadPlugin();
|
void unloadPlugin();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -102,6 +102,12 @@ void AutoTypeSelectDialog::setMatches(const QList<AutoTypeMatch>& matches, const
|
|||||||
m_ui->searchCheckBox->setChecked(m_matches.isEmpty());
|
m_ui->searchCheckBox->setChecked(m_matches.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::setSearchString(const QString& search)
|
||||||
|
{
|
||||||
|
m_ui->search->setText(search);
|
||||||
|
m_ui->searchCheckBox->setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
|
void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
|
||||||
{
|
{
|
||||||
if (match.first) {
|
if (match.first) {
|
||||||
|
@ -40,6 +40,7 @@ public:
|
|||||||
~AutoTypeSelectDialog() override;
|
~AutoTypeSelectDialog() override;
|
||||||
|
|
||||||
void setMatches(const QList<AutoTypeMatch>& matchList, const QList<QSharedPointer<Database>>& dbs);
|
void setMatches(const QList<AutoTypeMatch>& matchList, const QList<QSharedPointer<Database>>& dbs);
|
||||||
|
void setSearchString(const QString& search);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void matchActivated(AutoTypeMatch match);
|
void matchActivated(AutoTypeMatch match);
|
||||||
|
@ -57,6 +57,8 @@ namespace
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int BrowserAction::MaxUrlLength = 256;
|
||||||
|
|
||||||
QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
|
QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
|
||||||
{
|
{
|
||||||
if (json.isEmpty()) {
|
if (json.isEmpty()) {
|
||||||
@ -65,7 +67,7 @@ QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
|
|||||||
|
|
||||||
bool triggerUnlock = false;
|
bool triggerUnlock = false;
|
||||||
const QString trigger = json.value("triggerUnlock").toString();
|
const QString trigger = json.value("triggerUnlock").toString();
|
||||||
if (!trigger.isEmpty() && trigger.compare(TRUE_STR, Qt::CaseSensitive) == 0) {
|
if (!trigger.isEmpty() && trigger.compare(TRUE_STR) == 0) {
|
||||||
triggerUnlock = true;
|
triggerUnlock = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +76,8 @@ QJsonObject BrowserAction::processClientMessage(const QJsonObject& json)
|
|||||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !browserService()->isDatabaseOpened()) {
|
if (action.compare("change-public-keys") != 0 && action.compare("request-autotype") != 0
|
||||||
|
&& !browserService()->isDatabaseOpened()) {
|
||||||
if (m_clientPublicKey.isEmpty()) {
|
if (m_clientPublicKey.isEmpty()) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||||
} else if (!browserService()->openDatabase(triggerUnlock)) {
|
} else if (!browserService()->openDatabase(triggerUnlock)) {
|
||||||
@ -92,30 +95,32 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json)
|
|||||||
{
|
{
|
||||||
QString action = json.value("action").toString();
|
QString action = json.value("action").toString();
|
||||||
|
|
||||||
if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
|
if (action.compare("change-public-keys") == 0) {
|
||||||
return handleChangePublicKeys(json, action);
|
return handleChangePublicKeys(json, action);
|
||||||
} else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("get-databasehash") == 0) {
|
||||||
return handleGetDatabaseHash(json, action);
|
return handleGetDatabaseHash(json, action);
|
||||||
} else if (action.compare("associate", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("associate") == 0) {
|
||||||
return handleAssociate(json, action);
|
return handleAssociate(json, action);
|
||||||
} else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("test-associate") == 0) {
|
||||||
return handleTestAssociate(json, action);
|
return handleTestAssociate(json, action);
|
||||||
} else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("get-logins") == 0) {
|
||||||
return handleGetLogins(json, action);
|
return handleGetLogins(json, action);
|
||||||
} else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("generate-password") == 0) {
|
||||||
return handleGeneratePassword(json, action);
|
return handleGeneratePassword(json, action);
|
||||||
} else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("set-login") == 0) {
|
||||||
return handleSetLogin(json, action);
|
return handleSetLogin(json, action);
|
||||||
} else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("lock-database") == 0) {
|
||||||
return handleLockDatabase(json, action);
|
return handleLockDatabase(json, action);
|
||||||
} else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("get-database-groups") == 0) {
|
||||||
return handleGetDatabaseGroups(json, action);
|
return handleGetDatabaseGroups(json, action);
|
||||||
} else if (action.compare("create-new-group", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("create-new-group") == 0) {
|
||||||
return handleCreateNewGroup(json, action);
|
return handleCreateNewGroup(json, action);
|
||||||
} else if (action.compare("get-totp", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("get-totp") == 0) {
|
||||||
return handleGetTotp(json, action);
|
return handleGetTotp(json, action);
|
||||||
} else if (action.compare("delete-entry", Qt::CaseSensitive) == 0) {
|
} else if (action.compare("delete-entry") == 0) {
|
||||||
return handleDeleteEntry(json, action);
|
return handleDeleteEntry(json, action);
|
||||||
|
} else if (action.compare("request-autotype") == 0) {
|
||||||
|
return handleGlobalAutoType(json, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action was not recognized
|
// Action was not recognized
|
||||||
@ -169,7 +174,7 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString command = decrypted.value("action").toString();
|
QString command = decrypted.value("action").toString();
|
||||||
if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
|
if (!command.isEmpty() && command.compare("get-databasehash") == 0) {
|
||||||
const QString newNonce = incrementNonce(nonce);
|
const QString newNonce = incrementNonce(nonce);
|
||||||
|
|
||||||
QJsonObject message = buildMessage(newNonce);
|
QJsonObject message = buildMessage(newNonce);
|
||||||
@ -206,7 +211,7 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
|
|||||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
|
if (key.compare(m_clientPublicKey) == 0) {
|
||||||
// Check for identification key. If it's not found, ensure backwards compatibility and use the current public
|
// Check for identification key. If it's not found, ensure backwards compatibility and use the current public
|
||||||
// key
|
// key
|
||||||
const QString idKey = decrypted.value("idKey").toString();
|
const QString idKey = decrypted.value("idKey").toString();
|
||||||
@ -245,7 +250,7 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS
|
|||||||
}
|
}
|
||||||
|
|
||||||
const QString key = browserService()->getKey(id);
|
const QString key = browserService()->getKey(id);
|
||||||
if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
|
if (key.isEmpty() || key.compare(responseKey) != 0) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +295,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
|||||||
const QString id = decrypted.value("id").toString();
|
const QString id = decrypted.value("id").toString();
|
||||||
const QString formUrl = decrypted.value("submitUrl").toString();
|
const QString formUrl = decrypted.value("submitUrl").toString();
|
||||||
const QString auth = decrypted.value("httpAuth").toString();
|
const QString auth = decrypted.value("httpAuth").toString();
|
||||||
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0;
|
const bool httpAuth = auth.compare(TRUE_STR) == 0;
|
||||||
const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
|
const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
|
||||||
|
|
||||||
if (users.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
@ -398,7 +403,7 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString command = decrypted.value("action").toString();
|
QString command = decrypted.value("action").toString();
|
||||||
if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
|
if (!command.isEmpty() && command.compare("lock-database") == 0) {
|
||||||
browserService()->lockDatabase();
|
browserService()->lockDatabase();
|
||||||
|
|
||||||
const QString newNonce = incrementNonce(nonce);
|
const QString newNonce = incrementNonce(nonce);
|
||||||
@ -426,7 +431,7 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString command = decrypted.value("action").toString();
|
QString command = decrypted.value("action").toString();
|
||||||
if (command.isEmpty() || command.compare("get-database-groups", Qt::CaseSensitive) != 0) {
|
if (command.isEmpty() || command.compare("get-database-groups") != 0) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +464,7 @@ QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const Q
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString command = decrypted.value("action").toString();
|
QString command = decrypted.value("action").toString();
|
||||||
if (command.isEmpty() || command.compare("create-new-group", Qt::CaseSensitive) != 0) {
|
if (command.isEmpty() || command.compare("create-new-group") != 0) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,7 +498,7 @@ QJsonObject BrowserAction::handleGetTotp(const QJsonObject& json, const QString&
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString command = decrypted.value("action").toString();
|
QString command = decrypted.value("action").toString();
|
||||||
if (command.isEmpty() || command.compare("get-totp", Qt::CaseSensitive) != 0) {
|
if (command.isEmpty() || command.compare("get-totp") != 0) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,7 +532,7 @@ QJsonObject BrowserAction::handleDeleteEntry(const QJsonObject& json, const QStr
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString command = decrypted.value("action").toString();
|
QString command = decrypted.value("action").toString();
|
||||||
if (command.isEmpty() || command.compare("delete-entry", Qt::CaseSensitive) != 0) {
|
if (command.isEmpty() || command.compare("delete-entry") != 0) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,6 +550,33 @@ QJsonObject BrowserAction::handleDeleteEntry(const QJsonObject& json, const QStr
|
|||||||
return buildResponse(action, message, newNonce);
|
return buildResponse(action, message, newNonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject BrowserAction::handleGlobalAutoType(const QJsonObject& json, const QString& action)
|
||||||
|
{
|
||||||
|
const QString nonce = json.value("nonce").toString();
|
||||||
|
const QString encrypted = json.value("message").toString();
|
||||||
|
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||||
|
|
||||||
|
if (decrypted.isEmpty()) {
|
||||||
|
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString command = decrypted.value("action").toString();
|
||||||
|
if (command.isEmpty() || command.compare("request-autotype") != 0) {
|
||||||
|
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto topLevelDomain = decrypted.value("search").toString();
|
||||||
|
if (topLevelDomain.length() > BrowserAction::MaxUrlLength) {
|
||||||
|
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
browserService()->requestGlobalAutoType(topLevelDomain);
|
||||||
|
|
||||||
|
const QString newNonce = incrementNonce(nonce);
|
||||||
|
QJsonObject message = buildMessage(newNonce);
|
||||||
|
return buildResponse(action, message, newNonce);
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
|
QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
|
||||||
{
|
{
|
||||||
QJsonObject response;
|
QJsonObject response;
|
||||||
|
@ -44,6 +44,7 @@ private:
|
|||||||
QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action);
|
QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleGetTotp(const QJsonObject& json, const QString& action);
|
QJsonObject handleGetTotp(const QJsonObject& json, const QString& action);
|
||||||
QJsonObject handleDeleteEntry(const QJsonObject& json, const QString& action);
|
QJsonObject handleDeleteEntry(const QJsonObject& json, const QString& action);
|
||||||
|
QJsonObject handleGlobalAutoType(const QJsonObject& json, const QString& action);
|
||||||
|
|
||||||
QJsonObject buildMessage(const QString& nonce) const;
|
QJsonObject buildMessage(const QString& nonce) const;
|
||||||
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
||||||
@ -63,6 +64,8 @@ private:
|
|||||||
QString incrementNonce(const QString& nonce);
|
QString incrementNonce(const QString& nonce);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static const int MaxUrlLength;
|
||||||
|
|
||||||
QString m_clientPublicKey;
|
QString m_clientPublicKey;
|
||||||
QString m_publicKey;
|
QString m_publicKey;
|
||||||
QString m_secretKey;
|
QString m_secretKey;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BrowserService.h"
|
#include "BrowserService.h"
|
||||||
|
|
||||||
#include "BrowserAccessControlDialog.h"
|
#include "BrowserAccessControlDialog.h"
|
||||||
#include "BrowserAction.h"
|
#include "BrowserAction.h"
|
||||||
#include "BrowserEntryConfig.h"
|
#include "BrowserEntryConfig.h"
|
||||||
@ -28,6 +27,7 @@
|
|||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
#include "gui/MainWindow.h"
|
#include "gui/MainWindow.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
|
#include "gui/osutils/OSUtils.h"
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
#include "gui/osutils/macutils/MacUtils.h"
|
#include "gui/osutils/macutils/MacUtils.h"
|
||||||
#endif
|
#endif
|
||||||
@ -761,6 +761,11 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> db)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BrowserService::requestGlobalAutoType(const QString& search)
|
||||||
|
{
|
||||||
|
emit osUtils->globalShortcutTriggered("autotype", search);
|
||||||
|
}
|
||||||
|
|
||||||
QList<Entry*>
|
QList<Entry*>
|
||||||
BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr, const QString& formUrlStr)
|
BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr, const QString& formUrlStr)
|
||||||
{
|
{
|
||||||
@ -1012,6 +1017,12 @@ bool BrowserService::schemeFound(const QString& url)
|
|||||||
return !address.scheme().isEmpty();
|
return !address.scheme().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BrowserService::isIpAddress(const QString& host) const
|
||||||
|
{
|
||||||
|
QHostAddress address(host);
|
||||||
|
return address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol;
|
||||||
|
}
|
||||||
|
|
||||||
bool BrowserService::removeFirstDomain(QString& hostname)
|
bool BrowserService::removeFirstDomain(QString& hostname)
|
||||||
{
|
{
|
||||||
int pos = hostname.indexOf(".");
|
int pos = hostname.indexOf(".");
|
||||||
@ -1088,7 +1099,7 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match the base domain
|
// Match the base domain
|
||||||
if (baseDomain(siteQUrl.host()) != baseDomain(entryQUrl.host())) {
|
if (getTopLevelDomainFromUrl(siteQUrl.host()) != getTopLevelDomainFromUrl(entryQUrl.host())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1105,15 +1116,14 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt
|
|||||||
*
|
*
|
||||||
* Returns the base domain, e.g. https://another.example.co.uk -> example.co.uk
|
* Returns the base domain, e.g. https://another.example.co.uk -> example.co.uk
|
||||||
*/
|
*/
|
||||||
QString BrowserService::baseDomain(const QString& hostname) const
|
QString BrowserService::getTopLevelDomainFromUrl(const QString& url) const
|
||||||
{
|
{
|
||||||
QUrl qurl = QUrl::fromUserInput(hostname);
|
QUrl qurl = QUrl::fromUserInput(url);
|
||||||
QString host = qurl.host();
|
QString host = qurl.host();
|
||||||
|
|
||||||
// If the hostname is an IP address, return it directly
|
// If the hostname is an IP address, return it directly
|
||||||
QHostAddress hostAddress(hostname);
|
if (isIpAddress(host)) {
|
||||||
if (!hostAddress.isNull()) {
|
return host;
|
||||||
return hostname;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.isEmpty() || !host.contains(qurl.topLevelDomain())) {
|
if (host.isEmpty() || !host.contains(qurl.topLevelDomain())) {
|
||||||
|
@ -80,6 +80,7 @@ public:
|
|||||||
const StringPairList& keyList,
|
const StringPairList& keyList,
|
||||||
const bool httpAuth = false);
|
const bool httpAuth = false);
|
||||||
|
|
||||||
|
void requestGlobalAutoType(const QString& search);
|
||||||
static void convertAttributesToCustomData(QSharedPointer<Database> db);
|
static void convertAttributesToCustomData(QSharedPointer<Database> db);
|
||||||
|
|
||||||
static const QString KEEPASSXCBROWSER_NAME;
|
static const QString KEEPASSXCBROWSER_NAME;
|
||||||
@ -132,9 +133,11 @@ private:
|
|||||||
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
||||||
int sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr);
|
int sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr);
|
||||||
bool schemeFound(const QString& url);
|
bool schemeFound(const QString& url);
|
||||||
|
bool isIpAddress(const QString& host) const;
|
||||||
bool removeFirstDomain(QString& hostname);
|
bool removeFirstDomain(QString& hostname);
|
||||||
bool handleEntry(Entry* entry, const QString& url, const QString& submitUrl);
|
bool handleEntry(Entry* entry, const QString& url, const QString& submitUrl);
|
||||||
bool handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr);
|
bool handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr);
|
||||||
|
QString getTopLevelDomainFromUrl(const QString& url) const;
|
||||||
QString baseDomain(const QString& hostname) const;
|
QString baseDomain(const QString& hostname) const;
|
||||||
QSharedPointer<Database> getDatabase();
|
QSharedPointer<Database> getDatabase();
|
||||||
QSharedPointer<Database> selectedDatabase();
|
QSharedPointer<Database> selectedDatabase();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -47,7 +47,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
|||||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActiveDatabaseChanged()));
|
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActiveDatabaseChanged()));
|
||||||
connect(this, SIGNAL(activeDatabaseChanged(DatabaseWidget*)),
|
connect(this, SIGNAL(activeDatabaseChanged(DatabaseWidget*)),
|
||||||
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
|
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
|
||||||
connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType()));
|
connect(autoType(), SIGNAL(globalAutoTypeTriggered(const QString&)), SLOT(performGlobalAutoType(const QString&)));
|
||||||
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
||||||
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
|
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
|
||||||
connect(m_databaseOpenDialog.data(), &DatabaseOpenDialog::dialogFinished,
|
connect(m_databaseOpenDialog.data(), &DatabaseOpenDialog::dialogFinished,
|
||||||
@ -790,28 +790,30 @@ void DatabaseTabWidget::emitDatabaseLockChanged()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseTabWidget::performGlobalAutoType()
|
void DatabaseTabWidget::performGlobalAutoType(const QString& search)
|
||||||
{
|
{
|
||||||
auto currentDbWidget = currentDatabaseWidget();
|
auto currentDbWidget = currentDatabaseWidget();
|
||||||
if (!currentDbWidget) {
|
if (!currentDbWidget) {
|
||||||
// no open databases, nothing to do
|
// No open databases, nothing to do
|
||||||
return;
|
return;
|
||||||
} else if (currentDbWidget->isLocked()) {
|
} else if (currentDbWidget->isLocked()) {
|
||||||
// Current database tab is locked, match behavior of browser unlock - prompt with
|
// Current database tab is locked, match behavior of browser unlock - prompt with
|
||||||
// the unlock dialog even if there are additional unlocked open database tabs.
|
// the unlock dialog even if there are additional unlocked open database tabs.
|
||||||
|
currentDbWidget->setSearchStringForAutoType(search);
|
||||||
unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::AutoType);
|
unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::AutoType);
|
||||||
} else {
|
} else {
|
||||||
// current database is unlocked, use it for AutoType along with any other unlocked databases
|
// Current database is unlocked, use it for AutoType along with any other unlocked databases
|
||||||
QList<QSharedPointer<Database>> unlockedDatabases;
|
QList<QSharedPointer<Database>> unlockedDatabases;
|
||||||
for (int i = 0, c = count(); i < c; ++i) {
|
for (int i = 0, c = count(); i < c; ++i) {
|
||||||
auto* dbWidget = databaseWidgetFromIndex(i);
|
auto* dbWidget = databaseWidgetFromIndex(i);
|
||||||
if (!dbWidget->isLocked()) {
|
if (!dbWidget->isLocked()) {
|
||||||
|
dbWidget->setSearchStringForAutoType(search);
|
||||||
unlockedDatabases.append(dbWidget->database());
|
unlockedDatabases.append(dbWidget->database());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(!unlockedDatabases.isEmpty());
|
Q_ASSERT(!unlockedDatabases.isEmpty());
|
||||||
autoType()->performGlobalAutoType(unlockedDatabases);
|
autoType()->performGlobalAutoType(unlockedDatabases, search);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -80,7 +80,7 @@ public slots:
|
|||||||
void showDatabaseSecurity();
|
void showDatabaseSecurity();
|
||||||
void showDatabaseReports();
|
void showDatabaseReports();
|
||||||
void showDatabaseSettings();
|
void showDatabaseSettings();
|
||||||
void performGlobalAutoType();
|
void performGlobalAutoType(const QString& search);
|
||||||
void performBrowserUnlock();
|
void performBrowserUnlock();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -186,7 +186,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
|||||||
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||||
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
||||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
|
connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
|
||||||
connect(this, SIGNAL(requestGlobalAutoType()), parent, SLOT(performGlobalAutoType()));
|
connect(this, SIGNAL(requestGlobalAutoType(const QString&)), parent, SLOT(performGlobalAutoType(const QString&)));
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
connectDatabaseSignals();
|
connectDatabaseSignals();
|
||||||
@ -309,6 +309,11 @@ void DatabaseWidget::setPreviewSplitterSizes(const QList<int>& sizes)
|
|||||||
m_previewSplitter->setSizes(sizes);
|
m_previewSplitter->setSizes(sizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatabaseWidget::setSearchStringForAutoType(const QString& search)
|
||||||
|
{
|
||||||
|
m_searchStringForAutoType = search;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current view state of entry view
|
* Get current view state of entry view
|
||||||
*/
|
*/
|
||||||
@ -1118,7 +1123,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
|||||||
// Rather than starting AutoType directly for this database, signal the parent DatabaseTabWidget to
|
// Rather than starting AutoType directly for this database, signal the parent DatabaseTabWidget to
|
||||||
// restart AutoType now that this database is unlocked, so that other open+unlocked databases
|
// restart AutoType now that this database is unlocked, so that other open+unlocked databases
|
||||||
// can be included in the search.
|
// can be included in the search.
|
||||||
emit requestGlobalAutoType();
|
emit requestGlobalAutoType(m_searchStringForAutoType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +121,7 @@ public:
|
|||||||
void setMainSplitterSizes(const QList<int>& sizes);
|
void setMainSplitterSizes(const QList<int>& sizes);
|
||||||
QList<int> previewSplitterSizes() const;
|
QList<int> previewSplitterSizes() const;
|
||||||
void setPreviewSplitterSizes(const QList<int>& sizes);
|
void setPreviewSplitterSizes(const QList<int>& sizes);
|
||||||
|
void setSearchStringForAutoType(const QString& search);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
// relayed Database signals
|
// relayed Database signals
|
||||||
@ -151,7 +152,7 @@ signals:
|
|||||||
void previewSplitterSizesChanged();
|
void previewSplitterSizesChanged();
|
||||||
void entryViewStateChanged();
|
void entryViewStateChanged();
|
||||||
void clearSearch();
|
void clearSearch();
|
||||||
void requestGlobalAutoType();
|
void requestGlobalAutoType(const QString& search);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool lock();
|
bool lock();
|
||||||
@ -297,6 +298,9 @@ private:
|
|||||||
|
|
||||||
// Autoreload
|
// Autoreload
|
||||||
bool m_blockAutoSave;
|
bool m_blockAutoSave;
|
||||||
|
|
||||||
|
// Auto-Type related
|
||||||
|
QString m_searchStringForAutoType;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_DATABASEWIDGET_H
|
#endif // KEEPASSX_DATABASEWIDGET_H
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -68,7 +68,7 @@ public:
|
|||||||
virtual bool setPreventScreenCapture(QWindow* window, bool allow) const;
|
virtual bool setPreventScreenCapture(QWindow* window, bool allow) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void globalShortcutTriggered(const QString& name);
|
void globalShortcutTriggered(const QString& name, const QString& search = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates platform UI theme change (light mode to dark mode).
|
* Indicates platform UI theme change (light mode to dark mode).
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -112,22 +112,49 @@ void TestBrowser::testIncrementNonce()
|
|||||||
/**
|
/**
|
||||||
* Tests for BrowserService
|
* Tests for BrowserService
|
||||||
*/
|
*/
|
||||||
void TestBrowser::testBaseDomain()
|
void TestBrowser::testTopLevelDomain()
|
||||||
{
|
{
|
||||||
QString url1 = "https://another.example.co.uk";
|
QString url1 = "https://another.example.co.uk";
|
||||||
QString url2 = "https://www.example.com";
|
QString url2 = "https://www.example.com";
|
||||||
QString url3 = "http://test.net";
|
QString url3 = "http://test.net";
|
||||||
QString url4 = "http://so.many.subdomains.co.jp";
|
QString url4 = "http://so.many.subdomains.co.jp";
|
||||||
|
QString url5 = "https://192.168.0.1";
|
||||||
|
QString url6 = "https://192.168.0.1:8000";
|
||||||
|
|
||||||
QString res1 = m_browserService->baseDomain(url1);
|
QString res1 = m_browserService->getTopLevelDomainFromUrl(url1);
|
||||||
QString res2 = m_browserService->baseDomain(url2);
|
QString res2 = m_browserService->getTopLevelDomainFromUrl(url2);
|
||||||
QString res3 = m_browserService->baseDomain(url3);
|
QString res3 = m_browserService->getTopLevelDomainFromUrl(url3);
|
||||||
QString res4 = m_browserService->baseDomain(url4);
|
QString res4 = m_browserService->getTopLevelDomainFromUrl(url4);
|
||||||
|
QString res5 = m_browserService->getTopLevelDomainFromUrl(url5);
|
||||||
|
QString res6 = m_browserService->getTopLevelDomainFromUrl(url6);
|
||||||
|
|
||||||
QCOMPARE(res1, QString("example.co.uk"));
|
QCOMPARE(res1, QString("example.co.uk"));
|
||||||
QCOMPARE(res2, QString("example.com"));
|
QCOMPARE(res2, QString("example.com"));
|
||||||
QCOMPARE(res3, QString("test.net"));
|
QCOMPARE(res3, QString("test.net"));
|
||||||
QCOMPARE(res4, QString("subdomains.co.jp"));
|
QCOMPARE(res4, QString("subdomains.co.jp"));
|
||||||
|
QCOMPARE(res5, QString("192.168.0.1"));
|
||||||
|
QCOMPARE(res6, QString("192.168.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestBrowser::testIsIpAddress()
|
||||||
|
{
|
||||||
|
auto host1 = "example.com"; // Not valid
|
||||||
|
auto host2 = "192.168.0.1";
|
||||||
|
auto host3 = "278.21.2.0"; // Not valid
|
||||||
|
auto host4 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
|
||||||
|
auto host5 = "2001:db8:0:1:1:1:1:1";
|
||||||
|
auto host6 = "fe80::1ff:fe23:4567:890a";
|
||||||
|
auto host7 = "2001:20::1";
|
||||||
|
auto host8 = "2001:0db8:85y3:0000:0000:8a2e:0370:7334"; // Not valid
|
||||||
|
|
||||||
|
QVERIFY(!m_browserService->isIpAddress(host1));
|
||||||
|
QVERIFY(m_browserService->isIpAddress(host2));
|
||||||
|
QVERIFY(!m_browserService->isIpAddress(host3));
|
||||||
|
QVERIFY(m_browserService->isIpAddress(host4));
|
||||||
|
QVERIFY(m_browserService->isIpAddress(host5));
|
||||||
|
QVERIFY(m_browserService->isIpAddress(host6));
|
||||||
|
QVERIFY(m_browserService->isIpAddress(host7));
|
||||||
|
QVERIFY(!m_browserService->isIpAddress(host8));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestBrowser::testSortPriority()
|
void TestBrowser::testSortPriority()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -37,7 +37,8 @@ private slots:
|
|||||||
void testGetBase64FromKey();
|
void testGetBase64FromKey();
|
||||||
void testIncrementNonce();
|
void testIncrementNonce();
|
||||||
|
|
||||||
void testBaseDomain();
|
void testTopLevelDomain();
|
||||||
|
void testIsIpAddress();
|
||||||
void testSortPriority();
|
void testSortPriority();
|
||||||
void testSortPriority_data();
|
void testSortPriority_data();
|
||||||
void testSearchEntries();
|
void testSearchEntries();
|
||||||
|
Loading…
Reference in New Issue
Block a user