mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Passkeys: Add support for importing Passkey to entry (#9987)
--------- Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
parent
013db199cb
commit
13c88e1013
@ -959,6 +959,15 @@ Do you want to delete the entry?
|
|||||||
<source>%1 (Passkey)</source>
|
<source>%1 (Passkey)</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>KeePassXC: Update Passkey</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Entry already has a Passkey.
|
||||||
|
Do you want to overwrite the Passkey in %1 - %2?</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>BrowserSettingsWidget</name>
|
<name>BrowserSettingsWidget</name>
|
||||||
@ -5992,10 +6001,6 @@ Do you want to overwrite it?
|
|||||||
<source>KeePassXC - Passkey Import</source>
|
<source>KeePassXC - Passkey Import</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Do you want to import the Passkey?</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>URL: %1</source>
|
<source>URL: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -6004,10 +6009,6 @@ Do you want to overwrite it?
|
|||||||
<source>Username: %1</source>
|
<source>Username: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Use default group (Imported Passkeys)</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Group</source>
|
<source>Group</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -6016,10 +6017,6 @@ Do you want to overwrite it?
|
|||||||
<source>Database</source>
|
<source>Database</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Select Database</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Import Passkey</source>
|
<source>Import Passkey</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -6033,11 +6030,23 @@ Do you want to overwrite it?
|
|||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Database: %1</source>
|
<source>Import the following Passkey:</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Group:</source>
|
<source>Entry</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Import the following Passkey to this entry:</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Create new entry</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Default Passkeys group (Imported Passkeys)</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
@ -6075,6 +6084,12 @@ Do you want to overwrite it?
|
|||||||
<source>Cannot import Passkey file "%1". Private key is missing or malformed.</source>
|
<source>Cannot import Passkey file "%1". Private key is missing or malformed.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Cannot import Passkey file "%1".
|
||||||
|
The following data is missing:
|
||||||
|
%2</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PasswordEditWidget</name>
|
<name>PasswordEditWidget</name>
|
||||||
|
@ -64,8 +64,6 @@ const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEnt
|
|||||||
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
|
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
|
||||||
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
|
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
|
||||||
const QString BrowserService::OPTION_OMIT_WWW = QStringLiteral("BrowserOmitWww");
|
const QString BrowserService::OPTION_OMIT_WWW = QStringLiteral("BrowserOmitWww");
|
||||||
// Multiple URL's
|
|
||||||
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
|
|
||||||
|
|
||||||
Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
||||||
|
|
||||||
@ -775,6 +773,20 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ask confirmation if entry already contains a Passkey
|
||||||
|
if (entry->hasPasskey()) {
|
||||||
|
if (MessageBox::question(
|
||||||
|
m_currentDatabaseWidget,
|
||||||
|
tr("KeePassXC: Update Passkey"),
|
||||||
|
tr("Entry already has a Passkey.\nDo you want to overwrite the Passkey in %1 - %2?")
|
||||||
|
.arg(entry->title(), entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME)),
|
||||||
|
MessageBox::Overwrite | MessageBox::Cancel,
|
||||||
|
MessageBox::Cancel)
|
||||||
|
!= MessageBox::Overwrite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
entry->beginUpdate();
|
entry->beginUpdate();
|
||||||
|
|
||||||
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
|
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
|
||||||
@ -1084,7 +1096,13 @@ void BrowserService::denyEntry(Entry* entry, const QString& siteHost, const QStr
|
|||||||
QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
||||||
{
|
{
|
||||||
QJsonObject res;
|
QJsonObject res;
|
||||||
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
|
// Use Passkey's username instead if found
|
||||||
|
res["login"] = entry->hasPasskey() ? entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME)
|
||||||
|
: entry->resolveMultiplePlaceholders(entry->username());
|
||||||
|
#else
|
||||||
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
|
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
|
||||||
|
#endif
|
||||||
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
|
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
|
||||||
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
|
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
|
||||||
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
||||||
@ -1291,8 +1309,7 @@ QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const Strin
|
|||||||
{
|
{
|
||||||
QList<Entry*> entries;
|
QList<Entry*> entries;
|
||||||
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
|
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
|
||||||
if (entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM)
|
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
||||||
&& entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
|
||||||
entries << entry;
|
entries << entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1419,14 +1436,34 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Database> BrowserService::getDatabase()
|
QSharedPointer<Database> BrowserService::getDatabase(const QUuid& rootGroupUuid)
|
||||||
{
|
{
|
||||||
|
if (!rootGroupUuid.isNull()) {
|
||||||
|
const auto openDatabases = getOpenDatabases();
|
||||||
|
for (const auto& db : openDatabases) {
|
||||||
|
if (db->rootGroup()->uuid() == rootGroupUuid) {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_currentDatabaseWidget) {
|
if (m_currentDatabaseWidget) {
|
||||||
return m_currentDatabaseWidget->database();
|
return m_currentDatabaseWidget->database();
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QSharedPointer<Database>> BrowserService::getOpenDatabases()
|
||||||
|
{
|
||||||
|
QList<QSharedPointer<Database>> databaseList;
|
||||||
|
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
||||||
|
if (!dbWidget->isLocked()) {
|
||||||
|
databaseList << dbWidget->database();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return databaseList;
|
||||||
|
}
|
||||||
|
|
||||||
QSharedPointer<Database> BrowserService::selectedDatabase()
|
QSharedPointer<Database> BrowserService::selectedDatabase()
|
||||||
{
|
{
|
||||||
QList<DatabaseWidget*> databaseWidgets;
|
QList<DatabaseWidget*> databaseWidgets;
|
||||||
|
@ -84,7 +84,9 @@ public:
|
|||||||
QString getCurrentTotp(const QString& uuid);
|
QString getCurrentTotp(const QString& uuid);
|
||||||
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
||||||
bool isPasswordGeneratorRequested() const;
|
bool isPasswordGeneratorRequested() const;
|
||||||
|
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
|
||||||
QSharedPointer<Database> selectedDatabase();
|
QSharedPointer<Database> selectedDatabase();
|
||||||
|
QList<QSharedPointer<Database>> getOpenDatabases();
|
||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
QJsonObject
|
QJsonObject
|
||||||
showPasskeysRegisterPrompt(const QJsonObject& publicKey, const QString& origin, const StringPairList& keyList);
|
showPasskeysRegisterPrompt(const QJsonObject& publicKey, const QString& origin, const StringPairList& keyList);
|
||||||
@ -124,7 +126,6 @@ public:
|
|||||||
static const QString OPTION_ONLY_HTTP_AUTH;
|
static const QString OPTION_ONLY_HTTP_AUTH;
|
||||||
static const QString OPTION_NOT_HTTP_AUTH;
|
static const QString OPTION_NOT_HTTP_AUTH;
|
||||||
static const QString OPTION_OMIT_WWW;
|
static const QString OPTION_OMIT_WWW;
|
||||||
static const QString ADDITIONAL_URL;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void requestUnlock();
|
void requestUnlock();
|
||||||
@ -191,7 +192,6 @@ private:
|
|||||||
const QString& siteUrl,
|
const QString& siteUrl,
|
||||||
const QString& formUrl,
|
const QString& formUrl,
|
||||||
const bool omitWwwSubdomain = false);
|
const bool omitWwwSubdomain = false);
|
||||||
QSharedPointer<Database> getDatabase();
|
|
||||||
QString getDatabaseRootUuid();
|
QString getDatabaseRootUuid();
|
||||||
QString getDatabaseRecycleBinUuid();
|
QString getDatabaseRecycleBinUuid();
|
||||||
bool checkLegacySettings(QSharedPointer<Database> db);
|
bool checkLegacySettings(QSharedPointer<Database> db);
|
||||||
|
@ -385,7 +385,8 @@ QStringList Entry::getAllUrls() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& key : m_attributes->keys()) {
|
for (const auto& key : m_attributes->keys()) {
|
||||||
if (key.startsWith("KP2A_URL")) {
|
if (key.startsWith(EntryAttributes::AdditionalUrlAttribute)
|
||||||
|
|| key == QString("%1_RELYING_PARTY").arg(EntryAttributes::PasskeyAttribute)) {
|
||||||
auto additionalUrl = m_attributes->value(key);
|
auto additionalUrl = m_attributes->value(key);
|
||||||
if (!additionalUrl.isEmpty()) {
|
if (!additionalUrl.isEmpty()) {
|
||||||
urlList << resolveMultiplePlaceholders(additionalUrl);
|
urlList << resolveMultiplePlaceholders(additionalUrl);
|
||||||
@ -545,6 +546,11 @@ bool Entry::hasTotp() const
|
|||||||
return !m_data.totpSettings.isNull();
|
return !m_data.totpSettings.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Entry::hasPasskey() const
|
||||||
|
{
|
||||||
|
return m_attributes->hasPasskey();
|
||||||
|
}
|
||||||
|
|
||||||
QString Entry::totp() const
|
QString Entry::totp() const
|
||||||
{
|
{
|
||||||
if (hasTotp()) {
|
if (hasTotp()) {
|
||||||
|
@ -121,6 +121,7 @@ public:
|
|||||||
void setExcludeFromReports(bool state);
|
void setExcludeFromReports(bool state);
|
||||||
|
|
||||||
bool hasTotp() const;
|
bool hasTotp() const;
|
||||||
|
bool hasPasskey() const;
|
||||||
bool isExpired() const;
|
bool isExpired() const;
|
||||||
bool willExpireInDays(int days) const;
|
bool willExpireInDays(int days) const;
|
||||||
bool isRecycled() const;
|
bool isRecycled() const;
|
||||||
|
@ -34,6 +34,7 @@ const QString EntryAttributes::SearchInGroupName = "SearchIn";
|
|||||||
const QString EntryAttributes::SearchTextGroupName = "SearchText";
|
const QString EntryAttributes::SearchTextGroupName = "SearchText";
|
||||||
|
|
||||||
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
||||||
|
const QString EntryAttributes::AdditionalUrlAttribute = "KP2A_URL";
|
||||||
const QString EntryAttributes::PasskeyAttribute = "KPEX_PASSKEY";
|
const QString EntryAttributes::PasskeyAttribute = "KPEX_PASSKEY";
|
||||||
|
|
||||||
EntryAttributes::EntryAttributes(QObject* parent)
|
EntryAttributes::EntryAttributes(QObject* parent)
|
||||||
@ -52,6 +53,18 @@ bool EntryAttributes::hasKey(const QString& key) const
|
|||||||
return m_attributes.contains(key);
|
return m_attributes.contains(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EntryAttributes::hasPasskey() const
|
||||||
|
{
|
||||||
|
const auto keyList = keys();
|
||||||
|
for (const auto& key : keyList) {
|
||||||
|
if (isPasskeyAttribute(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
QList<QString> EntryAttributes::customKeys() const
|
QList<QString> EntryAttributes::customKeys() const
|
||||||
{
|
{
|
||||||
QList<QString> customKeys;
|
QList<QString> customKeys;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||||
* Copyright (C) 2017 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
|
||||||
@ -33,6 +33,7 @@ public:
|
|||||||
explicit EntryAttributes(QObject* parent = nullptr);
|
explicit EntryAttributes(QObject* parent = nullptr);
|
||||||
QList<QString> keys() const;
|
QList<QString> keys() const;
|
||||||
bool hasKey(const QString& key) const;
|
bool hasKey(const QString& key) const;
|
||||||
|
bool hasPasskey() const;
|
||||||
QList<QString> customKeys() const;
|
QList<QString> customKeys() const;
|
||||||
QString value(const QString& key) const;
|
QString value(const QString& key) const;
|
||||||
QList<QString> values(const QList<QString>& keys) const;
|
QList<QString> values(const QList<QString>& keys) const;
|
||||||
@ -61,6 +62,7 @@ public:
|
|||||||
static const QString NotesKey;
|
static const QString NotesKey;
|
||||||
static const QStringList DefaultAttributes;
|
static const QStringList DefaultAttributes;
|
||||||
static const QString RememberCmdExecAttr;
|
static const QString RememberCmdExecAttr;
|
||||||
|
static const QString AdditionalUrlAttribute;
|
||||||
static const QString PasskeyAttribute;
|
static const QString PasskeyAttribute;
|
||||||
static bool isDefaultAttribute(const QString& key);
|
static bool isDefaultAttribute(const QString& key);
|
||||||
static bool isPasskeyAttribute(const QString& key);
|
static bool isPasskeyAttribute(const QString& key);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "core/Global.h"
|
#include "core/Global.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QList>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
@ -100,6 +101,19 @@ namespace Tools
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if all values are found inside the list. Returns a list of values not found.
|
||||||
|
template <typename T> QList<T> getMissingValuesFromList(const QList<T>& list, const QList<T>& required)
|
||||||
|
{
|
||||||
|
QList<T> missingValues;
|
||||||
|
for (const auto& r : required) {
|
||||||
|
if (!list.contains(r)) {
|
||||||
|
missingValues << r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return missingValues;
|
||||||
|
}
|
||||||
|
|
||||||
QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties = {"objectName"});
|
QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties = {"objectName"});
|
||||||
|
|
||||||
QString substituteBackupFilePath(QString pattern, const QString& databasePath);
|
QString substituteBackupFilePath(QString pattern, const QString& databasePath);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2023 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
|
||||||
@ -243,7 +243,8 @@ bool OpVaultReader::fillAttributes(Entry* entry, const QJsonObject& bandEntry)
|
|||||||
auto newUrl = urlObj["u"].toString();
|
auto newUrl = urlObj["u"].toString();
|
||||||
if (newUrl != url) {
|
if (newUrl != url) {
|
||||||
// Add this url if it isn't the base one
|
// Add this url if it isn't the base one
|
||||||
entry->attributes()->set(QString("KP2A_URL_%1").arg(i), newUrl);
|
entry->attributes()->set(
|
||||||
|
QString("%1_%2").arg(EntryAttributes::AdditionalUrlAttribute, QString::number(i)), newUrl);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -564,7 +564,12 @@ void DatabaseTabWidget::showPasskeys()
|
|||||||
|
|
||||||
void DatabaseTabWidget::importPasskey()
|
void DatabaseTabWidget::importPasskey()
|
||||||
{
|
{
|
||||||
currentDatabaseWidget()->switchToImportPasskey();
|
currentDatabaseWidget()->showImportPasskeyDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTabWidget::importPasskeyToEntry()
|
||||||
|
{
|
||||||
|
currentDatabaseWidget()->showImportPasskeyDialog(true);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -88,6 +88,7 @@ public slots:
|
|||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
void showPasskeys();
|
void showPasskeys();
|
||||||
void importPasskey();
|
void importPasskey();
|
||||||
|
void importPasskeyToEntry();
|
||||||
#endif
|
#endif
|
||||||
void performGlobalAutoType(const QString& search);
|
void performGlobalAutoType(const QString& search);
|
||||||
void performBrowserUnlock();
|
void performBrowserUnlock();
|
||||||
|
@ -1407,10 +1407,20 @@ void DatabaseWidget::switchToPasskeys()
|
|||||||
m_reportsDialog->activatePasskeysPage();
|
m_reportsDialog->activatePasskeysPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseWidget::switchToImportPasskey()
|
void DatabaseWidget::showImportPasskeyDialog(bool isEntry)
|
||||||
{
|
{
|
||||||
PasskeyImporter passkeyImporter;
|
PasskeyImporter passkeyImporter;
|
||||||
passkeyImporter.importPasskey(m_db);
|
|
||||||
|
if (isEntry) {
|
||||||
|
auto currentEntry = currentSelectedEntry();
|
||||||
|
if (!currentEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
passkeyImporter.importPasskey(m_db, currentEntry);
|
||||||
|
} else {
|
||||||
|
passkeyImporter.importPasskey(m_db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ public slots:
|
|||||||
void switchToDatabaseSettings();
|
void switchToDatabaseSettings();
|
||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
void switchToPasskeys();
|
void switchToPasskeys();
|
||||||
void switchToImportPasskey();
|
void showImportPasskeyDialog(bool isEntry = false);
|
||||||
#endif
|
#endif
|
||||||
void switchToOpenDatabase();
|
void switchToOpenDatabase();
|
||||||
void switchToOpenDatabase(const QString& filePath);
|
void switchToOpenDatabase(const QString& filePath);
|
||||||
|
@ -135,6 +135,10 @@ MainWindow::MainWindow()
|
|||||||
m_entryContextMenu->addSeparator();
|
m_entryContextMenu->addSeparator();
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
|
m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
|
||||||
m_entryContextMenu->addSeparator();
|
m_entryContextMenu->addSeparator();
|
||||||
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
|
m_entryContextMenu->addAction(m_ui->actionEntryImportPasskey);
|
||||||
|
m_entryContextMenu->addSeparator();
|
||||||
|
#endif
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryEdit);
|
m_entryContextMenu->addAction(m_ui->actionEntryEdit);
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryClone);
|
m_entryContextMenu->addAction(m_ui->actionEntryClone);
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryDelete);
|
m_entryContextMenu->addAction(m_ui->actionEntryDelete);
|
||||||
@ -441,6 +445,7 @@ MainWindow::MainWindow()
|
|||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
m_ui->actionPasskeys->setIcon(icons()->icon("passkey"));
|
m_ui->actionPasskeys->setIcon(icons()->icon("passkey"));
|
||||||
m_ui->actionImportPasskey->setIcon(icons()->icon("document-import"));
|
m_ui->actionImportPasskey->setIcon(icons()->icon("document-import"));
|
||||||
|
m_ui->actionEntryImportPasskey->setIcon(icons()->icon("document-import"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_actionMultiplexer.connect(
|
m_actionMultiplexer.connect(
|
||||||
@ -491,6 +496,7 @@ MainWindow::MainWindow()
|
|||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
connect(m_ui->actionPasskeys, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showPasskeys()));
|
connect(m_ui->actionPasskeys, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showPasskeys()));
|
||||||
connect(m_ui->actionImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskey()));
|
connect(m_ui->actionImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskey()));
|
||||||
|
connect(m_ui->actionEntryImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskeyToEntry()));
|
||||||
#endif
|
#endif
|
||||||
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importCsv()));
|
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importCsv()));
|
||||||
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importKeePass1Database()));
|
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importKeePass1Database()));
|
||||||
@ -989,6 +995,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
m_ui->actionPasskeys->setEnabled(true);
|
m_ui->actionPasskeys->setEnabled(true);
|
||||||
m_ui->actionImportPasskey->setEnabled(true);
|
m_ui->actionImportPasskey->setEnabled(true);
|
||||||
|
m_ui->actionEntryImportPasskey->setEnabled(true);
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XC_SSHAGENT
|
#ifdef WITH_XC_SSHAGENT
|
||||||
bool singleEntryHasSshKey =
|
bool singleEntryHasSshKey =
|
||||||
@ -1060,9 +1067,11 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||||
m_ui->actionPasskeys->setEnabled(false);
|
m_ui->actionPasskeys->setEnabled(false);
|
||||||
m_ui->actionImportPasskey->setEnabled(false);
|
m_ui->actionImportPasskey->setEnabled(false);
|
||||||
|
m_ui->actionEntryImportPasskey->setEnabled(false);
|
||||||
#else
|
#else
|
||||||
m_ui->actionPasskeys->setVisible(false);
|
m_ui->actionPasskeys->setVisible(false);
|
||||||
m_ui->actionImportPasskey->setVisible(false);
|
m_ui->actionImportPasskey->setVisible(false);
|
||||||
|
m_ui->actionEntryImportPasskey->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_searchWidgetAction->setEnabled(false);
|
m_searchWidgetAction->setEnabled(false);
|
||||||
|
@ -342,6 +342,8 @@
|
|||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEntryAutoType"/>
|
<addaction name="actionEntryAutoType"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionEntryImportPasskey"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEntryOpenUrl"/>
|
<addaction name="actionEntryOpenUrl"/>
|
||||||
<addaction name="actionEntryDownloadIcon"/>
|
<addaction name="actionEntryDownloadIcon"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
@ -730,6 +732,14 @@
|
|||||||
<string>Perform &Auto-Type</string>
|
<string>Perform &Auto-Type</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionEntryImportPasskey">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Import Passkey</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionEntryAutoTypeUsername">
|
<action name="actionEntryAutoTypeUsername">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||||
* 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
|
||||||
@ -329,11 +329,11 @@ void EditEntryWidget::insertURL()
|
|||||||
{
|
{
|
||||||
Q_ASSERT(!m_history);
|
Q_ASSERT(!m_history);
|
||||||
|
|
||||||
QString name(BrowserService::ADDITIONAL_URL);
|
QString name(EntryAttributes::AdditionalUrlAttribute);
|
||||||
int i = 1;
|
int i = 1;
|
||||||
|
|
||||||
while (m_entryAttributes->keys().contains(name)) {
|
while (m_entryAttributes->keys().contains(name)) {
|
||||||
name = QString("%1_%2").arg(BrowserService::ADDITIONAL_URL, QString::number(i));
|
name = QString("%1_%2").arg(EntryAttributes::AdditionalUrlAttribute, QString::number(i));
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
|||||||
const auto urlValid = urlTools()->isUrlValid(value);
|
const auto urlValid = urlTools()->isUrlValid(value);
|
||||||
|
|
||||||
// Check for duplicate URLs in the attribute list. Excludes the current key/value from the comparison.
|
// Check for duplicate URLs in the attribute list. Excludes the current key/value from the comparison.
|
||||||
auto customAttributeKeys = m_entryAttributes->customKeys().filter(BrowserService::ADDITIONAL_URL);
|
auto customAttributeKeys = m_entryAttributes->customKeys().filter(EntryAttributes::AdditionalUrlAttribute);
|
||||||
customAttributeKeys.removeOne(key);
|
customAttributeKeys.removeOne(key);
|
||||||
|
|
||||||
const auto duplicateUrl =
|
const auto duplicateUrl =
|
||||||
@ -148,7 +148,7 @@ void EntryURLModel::updateAttributes()
|
|||||||
|
|
||||||
const auto attributesKeyList = m_entryAttributes->keys();
|
const auto attributesKeyList = m_entryAttributes->keys();
|
||||||
for (const auto& key : attributesKeyList) {
|
for (const auto& key : attributesKeyList) {
|
||||||
if (!EntryAttributes::isDefaultAttribute(key) && key.contains(BrowserService::ADDITIONAL_URL)) {
|
if (!EntryAttributes::isDefaultAttribute(key) && key.contains(EntryAttributes::AdditionalUrlAttribute)) {
|
||||||
const auto value = m_entryAttributes->value(key);
|
const auto value = m_entryAttributes->value(key);
|
||||||
m_urls.append(qMakePair(key, value));
|
m_urls.append(qMakePair(key, value));
|
||||||
|
|
||||||
|
@ -27,33 +27,41 @@
|
|||||||
PasskeyImportDialog::PasskeyImportDialog(QWidget* parent)
|
PasskeyImportDialog::PasskeyImportDialog(QWidget* parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_ui(new Ui::PasskeyImportDialog())
|
, m_ui(new Ui::PasskeyImportDialog())
|
||||||
, m_useDefaultGroup(true)
|
|
||||||
{
|
{
|
||||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||||
|
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
m_ui->useDefaultGroupCheckbox->setChecked(true);
|
|
||||||
m_ui->selectGroupComboBox->setEnabled(false);
|
|
||||||
|
|
||||||
|
connect(this, SIGNAL(updateGroups()), this, SLOT(addGroups()));
|
||||||
|
connect(this, SIGNAL(updateEntries()), this, SLOT(addEntries()));
|
||||||
connect(m_ui->importButton, SIGNAL(clicked()), SLOT(accept()));
|
connect(m_ui->importButton, SIGNAL(clicked()), SLOT(accept()));
|
||||||
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject()));
|
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject()));
|
||||||
connect(m_ui->selectDatabaseButton, SIGNAL(clicked()), SLOT(selectDatabase()));
|
connect(m_ui->selectDatabaseCombobBox, SIGNAL(currentIndexChanged(int)), SLOT(changeDatabase(int)));
|
||||||
|
connect(m_ui->selectEntryComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeEntry(int)));
|
||||||
connect(m_ui->selectGroupComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeGroup(int)));
|
connect(m_ui->selectGroupComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeGroup(int)));
|
||||||
connect(m_ui->useDefaultGroupCheckbox, SIGNAL(stateChanged(int)), SLOT(useDefaultGroupChanged()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PasskeyImportDialog::~PasskeyImportDialog()
|
PasskeyImportDialog::~PasskeyImportDialog()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasskeyImportDialog::setInfo(const QString& url, const QString& username, const QSharedPointer<Database>& database)
|
void PasskeyImportDialog::setInfo(const QString& url,
|
||||||
|
const QString& username,
|
||||||
|
const QSharedPointer<Database>& database,
|
||||||
|
bool isEntry)
|
||||||
{
|
{
|
||||||
m_ui->urlLabel->setText(tr("URL: %1").arg(url));
|
m_ui->urlLabel->setText(tr("URL: %1").arg(url));
|
||||||
m_ui->usernameLabel->setText(tr("Username: %1").arg(username));
|
m_ui->usernameLabel->setText(tr("Username: %1").arg(username));
|
||||||
m_ui->selectDatabaseLabel->setText(tr("Database: %1").arg(getDatabaseName(database)));
|
|
||||||
m_ui->selectGroupLabel->setText(tr("Group:"));
|
|
||||||
|
|
||||||
addGroups(database);
|
if (isEntry) {
|
||||||
|
m_ui->verticalLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
|
m_ui->infoLabel->setText(tr("Import the following Passkey to this entry:"));
|
||||||
|
m_ui->groupBox->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_selectedDatabase = database;
|
||||||
|
addDatabases();
|
||||||
|
addGroups();
|
||||||
|
|
||||||
auto openDatabaseCount = 0;
|
auto openDatabaseCount = 0;
|
||||||
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
||||||
@ -61,34 +69,96 @@ void PasskeyImportDialog::setInfo(const QString& url, const QString& username, c
|
|||||||
openDatabaseCount++;
|
openDatabaseCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_ui->selectDatabaseButton->setEnabled(openDatabaseCount > 1);
|
m_ui->selectDatabaseCombobBox->setEnabled(openDatabaseCount > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Database> PasskeyImportDialog::getSelectedDatabase()
|
QSharedPointer<Database> PasskeyImportDialog::getSelectedDatabase() const
|
||||||
{
|
{
|
||||||
return m_selectedDatabase;
|
return m_selectedDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid PasskeyImportDialog::getSelectedGroupUuid()
|
QUuid PasskeyImportDialog::getSelectedEntryUuid() const
|
||||||
|
{
|
||||||
|
return m_selectedEntryUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUuid PasskeyImportDialog::getSelectedGroupUuid() const
|
||||||
{
|
{
|
||||||
return m_selectedGroupUuid;
|
return m_selectedGroupUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PasskeyImportDialog::useDefaultGroup()
|
bool PasskeyImportDialog::useDefaultGroup() const
|
||||||
{
|
{
|
||||||
return m_useDefaultGroup;
|
return m_selectedGroupUuid.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PasskeyImportDialog::getDatabaseName(const QSharedPointer<Database>& database) const
|
bool PasskeyImportDialog::createNewEntry() const
|
||||||
{
|
{
|
||||||
return QFileInfo(database->filePath()).fileName();
|
return m_selectedEntryUuid.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasskeyImportDialog::addGroups(const QSharedPointer<Database>& database)
|
void PasskeyImportDialog::addDatabases()
|
||||||
{
|
{
|
||||||
|
auto currentDatabaseIndex = 0;
|
||||||
|
const auto openDatabases = browserService()->getOpenDatabases();
|
||||||
|
const auto currentDatabase = browserService()->getDatabase();
|
||||||
|
|
||||||
|
m_ui->selectDatabaseCombobBox->clear();
|
||||||
|
for (const auto& db : openDatabases) {
|
||||||
|
m_ui->selectDatabaseCombobBox->addItem(db->metadata()->name(), db->rootGroup()->uuid());
|
||||||
|
if (db->rootGroup()->uuid() == currentDatabase->rootGroup()->uuid()) {
|
||||||
|
currentDatabaseIndex = m_ui->selectDatabaseCombobBox->count() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->selectDatabaseCombobBox->setCurrentIndex(currentDatabaseIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PasskeyImportDialog::addEntries()
|
||||||
|
{
|
||||||
|
if (!m_selectedDatabase || !m_selectedDatabase->rootGroup()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->selectEntryComboBox->clear();
|
||||||
|
m_ui->selectEntryComboBox->addItem(tr("Create new entry"), {});
|
||||||
|
|
||||||
|
const auto group = m_selectedDatabase->rootGroup()->findGroupByUuid(m_selectedGroupUuid);
|
||||||
|
if (!group) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all entries in the group and resolve the title
|
||||||
|
QList<QPair<QString, QUuid>> entries;
|
||||||
|
for (const auto entry : group->entries()) {
|
||||||
|
if (!entry || entry->isRecycled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entries.append({entry->resolveMultiplePlaceholders(entry->title()), entry->uuid()});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort entries by title
|
||||||
|
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
|
||||||
|
return a.first.compare(b.first, Qt::CaseInsensitive) < 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add sorted entries to the combobox
|
||||||
|
for (const auto& pair : entries) {
|
||||||
|
m_ui->selectEntryComboBox->addItem(pair.first, pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PasskeyImportDialog::addGroups()
|
||||||
|
{
|
||||||
|
if (!m_selectedDatabase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_ui->selectGroupComboBox->clear();
|
m_ui->selectGroupComboBox->clear();
|
||||||
for (const auto& group : database->rootGroup()->groupsRecursive(true)) {
|
m_ui->selectGroupComboBox->addItem(tr("Default Passkeys group (Imported Passkeys)"), {});
|
||||||
if (!group || group->isRecycled() || group == database->metadata()->recycleBin()) {
|
|
||||||
|
for (const auto& group : m_selectedDatabase->rootGroup()->groupsRecursive(true)) {
|
||||||
|
if (!group || group->isRecycled() || group == m_selectedDatabase->metadata()->recycleBin()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,26 +166,20 @@ void PasskeyImportDialog::addGroups(const QSharedPointer<Database>& database)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasskeyImportDialog::selectDatabase()
|
void PasskeyImportDialog::changeDatabase(int index)
|
||||||
{
|
{
|
||||||
auto selectedDatabase = browserService()->selectedDatabase();
|
m_selectedDatabaseUuid = m_ui->selectDatabaseCombobBox->itemData(index).value<QUuid>();
|
||||||
if (!selectedDatabase) {
|
m_selectedDatabase = browserService()->getDatabase(m_selectedDatabaseUuid);
|
||||||
return;
|
emit updateGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_selectedDatabase = selectedDatabase;
|
void PasskeyImportDialog::changeEntry(int index)
|
||||||
m_ui->selectDatabaseLabel->setText(QString("Database: %1").arg(getDatabaseName(m_selectedDatabase)));
|
{
|
||||||
|
m_selectedEntryUuid = m_ui->selectEntryComboBox->itemData(index).value<QUuid>();
|
||||||
addGroups(m_selectedDatabase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasskeyImportDialog::changeGroup(int index)
|
void PasskeyImportDialog::changeGroup(int index)
|
||||||
{
|
{
|
||||||
m_selectedGroupUuid = m_ui->selectGroupComboBox->itemData(index).value<QUuid>();
|
m_selectedGroupUuid = m_ui->selectGroupComboBox->itemData(index).value<QUuid>();
|
||||||
}
|
emit updateEntries();
|
||||||
|
|
||||||
void PasskeyImportDialog::useDefaultGroupChanged()
|
|
||||||
{
|
|
||||||
m_ui->selectGroupComboBox->setEnabled(!m_ui->useDefaultGroupCheckbox->isChecked());
|
|
||||||
m_useDefaultGroup = m_ui->useDefaultGroupCheckbox->isChecked();
|
|
||||||
}
|
}
|
||||||
|
@ -36,25 +36,33 @@ public:
|
|||||||
explicit PasskeyImportDialog(QWidget* parent = nullptr);
|
explicit PasskeyImportDialog(QWidget* parent = nullptr);
|
||||||
~PasskeyImportDialog() override;
|
~PasskeyImportDialog() override;
|
||||||
|
|
||||||
void setInfo(const QString& url, const QString& username, const QSharedPointer<Database>& database);
|
void setInfo(const QString& url, const QString& username, const QSharedPointer<Database>& database, bool isEntry);
|
||||||
QSharedPointer<Database> getSelectedDatabase();
|
QSharedPointer<Database> getSelectedDatabase() const;
|
||||||
QUuid getSelectedGroupUuid();
|
QUuid getSelectedEntryUuid() const;
|
||||||
bool useDefaultGroup();
|
QUuid getSelectedGroupUuid() const;
|
||||||
|
bool useDefaultGroup() const;
|
||||||
|
bool createNewEntry() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString getDatabaseName(const QSharedPointer<Database>& database) const;
|
void addDatabases();
|
||||||
void addGroups(const QSharedPointer<Database>& database);
|
|
||||||
|
signals:
|
||||||
|
void updateEntries();
|
||||||
|
void updateGroups();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void selectDatabase();
|
void addEntries();
|
||||||
|
void addGroups();
|
||||||
|
void changeDatabase(int index);
|
||||||
|
void changeEntry(int index);
|
||||||
void changeGroup(int index);
|
void changeGroup(int index);
|
||||||
void useDefaultGroupChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QScopedPointer<Ui::PasskeyImportDialog> m_ui;
|
QScopedPointer<Ui::PasskeyImportDialog> m_ui;
|
||||||
QSharedPointer<Database> m_selectedDatabase;
|
QSharedPointer<Database> m_selectedDatabase;
|
||||||
|
QUuid m_selectedDatabaseUuid;
|
||||||
|
QUuid m_selectedEntryUuid;
|
||||||
QUuid m_selectedGroupUuid;
|
QUuid m_selectedGroupUuid;
|
||||||
bool m_useDefaultGroup;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSXC_PASSKEYIMPORTDIALOG_H
|
#endif // KEEPASSXC_PASSKEYIMPORTDIALOG_H
|
||||||
|
@ -6,10 +6,22 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>405</width>
|
<width>500</width>
|
||||||
<height>227</height>
|
<height>300</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>KeePassXC - Passkey Import</string>
|
<string>KeePassXC - Passkey Import</string>
|
||||||
</property>
|
</property>
|
||||||
@ -24,7 +36,7 @@
|
|||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Do you want to import the Passkey?</string>
|
<string>Import the following Passkey:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||||
@ -52,80 +64,62 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="useDefaultGroupCheckbox">
|
<spacer name="verticalSpacer">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string>Use default group (Imported Passkeys)</string>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="checked">
|
<property name="sizeHint" stdset="0">
|
||||||
<bool>false</bool>
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>10</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="selectGroupHorLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="databaseLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Database</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="selectDatabaseCombobBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="groupLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Group</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="selectGroupComboBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="entryLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Entry</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="selectEntryComboBox"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="selectGroupHorLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="selectGroupLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Group</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="selectGroupComboBox"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="selectDatabaseHorLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="selectDatabaseLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Database</string>
|
|
||||||
</property>
|
|
||||||
<property name="scaledContents">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_3">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="selectDatabaseButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Select Database</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "browser/BrowserService.h"
|
#include "browser/BrowserService.h"
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
#include "gui/FileDialog.h"
|
#include "gui/FileDialog.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@ -29,7 +30,7 @@
|
|||||||
|
|
||||||
static const QString IMPORTED_PASSKEYS_GROUP = QStringLiteral("Imported Passkeys");
|
static const QString IMPORTED_PASSKEYS_GROUP = QStringLiteral("Imported Passkeys");
|
||||||
|
|
||||||
void PasskeyImporter::importPasskey(QSharedPointer<Database>& database)
|
void PasskeyImporter::importPasskey(QSharedPointer<Database>& database, Entry* entry)
|
||||||
{
|
{
|
||||||
auto filter = QString("%1 (*.passkey);;%2 (*)").arg(tr("Passkey file"), tr("All files"));
|
auto filter = QString("%1 (*.passkey);;%2 (*)").arg(tr("Passkey file"), tr("All files"));
|
||||||
auto fileName =
|
auto fileName =
|
||||||
@ -47,10 +48,10 @@ void PasskeyImporter::importPasskey(QSharedPointer<Database>& database)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
importSelectedFile(file, database);
|
importSelectedFile(file, database, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>& database)
|
void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>& database, Entry* entry)
|
||||||
{
|
{
|
||||||
const auto fileData = file.readAll();
|
const auto fileData = file.readAll();
|
||||||
const auto passkeyObject = browserMessageBuilder()->getJsonObject(fileData);
|
const auto passkeyObject = browserMessageBuilder()->getJsonObject(fileData);
|
||||||
@ -61,18 +62,20 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto relyingParty = passkeyObject["relyingParty"].toString();
|
|
||||||
const auto url = passkeyObject["url"].toString();
|
|
||||||
const auto username = passkeyObject["username"].toString();
|
|
||||||
const auto credentialId = passkeyObject["credentialId"].toString();
|
|
||||||
const auto userHandle = passkeyObject["userHandle"].toString();
|
|
||||||
const auto privateKey = passkeyObject["privateKey"].toString();
|
const auto privateKey = passkeyObject["privateKey"].toString();
|
||||||
|
const auto missingKeys = Tools::getMissingValuesFromList<QString>(passkeyObject.keys(),
|
||||||
|
QStringList() << "relyingParty"
|
||||||
|
<< "url"
|
||||||
|
<< "username"
|
||||||
|
<< "credentialId"
|
||||||
|
<< "userHandle"
|
||||||
|
<< "privateKey");
|
||||||
|
|
||||||
if (relyingParty.isEmpty() || username.isEmpty() || credentialId.isEmpty() || userHandle.isEmpty()
|
if (!missingKeys.isEmpty()) {
|
||||||
|| privateKey.isEmpty()) {
|
|
||||||
MessageBox::information(nullptr,
|
MessageBox::information(nullptr,
|
||||||
tr("Cannot import Passkey"),
|
tr("Cannot import Passkey"),
|
||||||
tr("Cannot import Passkey file \"%1\". Data is missing.").arg(file.fileName()));
|
tr("Cannot import Passkey file \"%1\".\nThe following data is missing:\n%2")
|
||||||
|
.arg(file.fileName(), missingKeys.join(", ")));
|
||||||
} else if (!privateKey.startsWith("-----BEGIN PRIVATE KEY-----")
|
} else if (!privateKey.startsWith("-----BEGIN PRIVATE KEY-----")
|
||||||
|| !privateKey.trimmed().endsWith("-----END PRIVATE KEY-----")) {
|
|| !privateKey.trimmed().endsWith("-----END PRIVATE KEY-----")) {
|
||||||
MessageBox::information(
|
MessageBox::information(
|
||||||
@ -80,7 +83,12 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
|
|||||||
tr("Cannot import Passkey"),
|
tr("Cannot import Passkey"),
|
||||||
tr("Cannot import Passkey file \"%1\". Private key is missing or malformed.").arg(file.fileName()));
|
tr("Cannot import Passkey file \"%1\". Private key is missing or malformed.").arg(file.fileName()));
|
||||||
} else {
|
} else {
|
||||||
showImportDialog(database, url, relyingParty, username, credentialId, userHandle, privateKey);
|
const auto relyingParty = passkeyObject["relyingParty"].toString();
|
||||||
|
const auto url = passkeyObject["url"].toString();
|
||||||
|
const auto username = passkeyObject["username"].toString();
|
||||||
|
const auto credentialId = passkeyObject["credentialId"].toString();
|
||||||
|
const auto userHandle = passkeyObject["userHandle"].toString();
|
||||||
|
showImportDialog(database, url, relyingParty, username, credentialId, userHandle, privateKey, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,10 +98,11 @@ void PasskeyImporter::showImportDialog(QSharedPointer<Database>& database,
|
|||||||
const QString& username,
|
const QString& username,
|
||||||
const QString& credentialId,
|
const QString& credentialId,
|
||||||
const QString& userHandle,
|
const QString& userHandle,
|
||||||
const QString& privateKey)
|
const QString& privateKey,
|
||||||
|
Entry* entry)
|
||||||
{
|
{
|
||||||
PasskeyImportDialog passkeyImportDialog;
|
PasskeyImportDialog passkeyImportDialog;
|
||||||
passkeyImportDialog.setInfo(relyingParty, username, database);
|
passkeyImportDialog.setInfo(relyingParty, username, database, entry != nullptr);
|
||||||
|
|
||||||
auto ret = passkeyImportDialog.exec();
|
auto ret = passkeyImportDialog.exec();
|
||||||
if (ret != QDialog::Accepted) {
|
if (ret != QDialog::Accepted) {
|
||||||
@ -105,6 +114,29 @@ void PasskeyImporter::showImportDialog(QSharedPointer<Database>& database,
|
|||||||
db = database;
|
db = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store to entry if given directly
|
||||||
|
if (entry) {
|
||||||
|
browserService()->addPasskeyToEntry(
|
||||||
|
entry, relyingParty, relyingParty, username, credentialId, userHandle, privateKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import to entry selected instead of creating a new one
|
||||||
|
if (!passkeyImportDialog.createNewEntry()) {
|
||||||
|
auto groupUuid = passkeyImportDialog.getSelectedGroupUuid();
|
||||||
|
auto group = db->rootGroup()->findGroupByUuid(groupUuid);
|
||||||
|
|
||||||
|
if (group) {
|
||||||
|
auto selectedEntry = group->findEntryByUuid(passkeyImportDialog.getSelectedEntryUuid());
|
||||||
|
if (selectedEntry) {
|
||||||
|
browserService()->addPasskeyToEntry(
|
||||||
|
selectedEntry, relyingParty, relyingParty, username, credentialId, userHandle, privateKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Group settings. Use default group "Imported Passkeys" if user did not select a specific one.
|
// Group settings. Use default group "Imported Passkeys" if user did not select a specific one.
|
||||||
Group* group = nullptr;
|
Group* group = nullptr;
|
||||||
|
|
||||||
@ -123,7 +155,7 @@ void PasskeyImporter::showImportDialog(QSharedPointer<Database>& database,
|
|||||||
group, url, relyingParty, relyingParty, username, credentialId, userHandle, privateKey);
|
group, url, relyingParty, relyingParty, username, credentialId, userHandle, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Group* PasskeyImporter::getDefaultGroup(QSharedPointer<Database>& database)
|
Group* PasskeyImporter::getDefaultGroup(QSharedPointer<Database>& database) const
|
||||||
{
|
{
|
||||||
auto defaultGroup = database->rootGroup()->findGroupByPath(IMPORTED_PASSKEYS_GROUP);
|
auto defaultGroup = database->rootGroup()->findGroupByPath(IMPORTED_PASSKEYS_GROUP);
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
class Entry;
|
class Entry;
|
||||||
|
|
||||||
@ -31,18 +32,19 @@ class PasskeyImporter : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit PasskeyImporter() = default;
|
explicit PasskeyImporter() = default;
|
||||||
|
|
||||||
void importPasskey(QSharedPointer<Database>& database);
|
void importPasskey(QSharedPointer<Database>& database, Entry* entry = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void importSelectedFile(QFile& file, QSharedPointer<Database>& database);
|
void importSelectedFile(QFile& file, QSharedPointer<Database>& database, Entry* entry);
|
||||||
void showImportDialog(QSharedPointer<Database>& database,
|
void showImportDialog(QSharedPointer<Database>& database,
|
||||||
const QString& url,
|
const QString& url,
|
||||||
const QString& relyingParty,
|
const QString& relyingParty,
|
||||||
const QString& username,
|
const QString& username,
|
||||||
const QString& credentialId,
|
const QString& credentialId,
|
||||||
const QString& userHandle,
|
const QString& userHandle,
|
||||||
const QString& privateKey);
|
const QString& privateKey,
|
||||||
Group* getDefaultGroup(QSharedPointer<Database>& database);
|
Entry* entry);
|
||||||
|
Group* getDefaultGroup(QSharedPointer<Database>& database) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSXC_PASSKEYIMPORTER_H
|
#endif // KEEPASSXC_PASSKEYIMPORTER_H
|
||||||
|
@ -341,8 +341,8 @@ void TestBrowser::testSearchEntriesByReference()
|
|||||||
auto secondEntryUuid = entries[1]->uuidToHex();
|
auto secondEntryUuid = entries[1]->uuidToHex();
|
||||||
auto fullReference = QString("{REF:A@I:%1}").arg(firstEntryUuid);
|
auto fullReference = QString("{REF:A@I:%1}").arg(firstEntryUuid);
|
||||||
auto partialReference = QString("https://subdomain.{REF:A@I:%1}").arg(secondEntryUuid);
|
auto partialReference = QString("https://subdomain.{REF:A@I:%1}").arg(secondEntryUuid);
|
||||||
entries[2]->attributes()->set(BrowserService::ADDITIONAL_URL, fullReference);
|
entries[2]->attributes()->set(EntryAttributes::AdditionalUrlAttribute, fullReference);
|
||||||
entries[3]->attributes()->set(BrowserService::ADDITIONAL_URL, partialReference);
|
entries[3]->attributes()->set(EntryAttributes::AdditionalUrlAttribute, partialReference);
|
||||||
entries[4]->setUrl(fullReference);
|
entries[4]->setUrl(fullReference);
|
||||||
entries[5]->setUrl(partialReference);
|
entries[5]->setUrl(partialReference);
|
||||||
|
|
||||||
@ -351,11 +351,13 @@ void TestBrowser::testSearchEntriesByReference()
|
|||||||
QCOMPARE(result[0]->url(), urls[0]);
|
QCOMPARE(result[0]->url(), urls[0]);
|
||||||
QCOMPARE(result[1]->url(), urls[1]);
|
QCOMPARE(result[1]->url(), urls[1]);
|
||||||
QCOMPARE(result[2]->url(), urls[2]);
|
QCOMPARE(result[2]->url(), urls[2]);
|
||||||
QCOMPARE(result[2]->resolveMultiplePlaceholders(result[2]->attributes()->value(BrowserService::ADDITIONAL_URL)),
|
QCOMPARE(
|
||||||
urls[0]);
|
result[2]->resolveMultiplePlaceholders(result[2]->attributes()->value(EntryAttributes::AdditionalUrlAttribute)),
|
||||||
|
urls[0]);
|
||||||
QCOMPARE(result[3]->url(), urls[3]);
|
QCOMPARE(result[3]->url(), urls[3]);
|
||||||
QCOMPARE(result[3]->resolveMultiplePlaceholders(result[3]->attributes()->value(BrowserService::ADDITIONAL_URL)),
|
QCOMPARE(
|
||||||
urls[0]);
|
result[3]->resolveMultiplePlaceholders(result[3]->attributes()->value(EntryAttributes::AdditionalUrlAttribute)),
|
||||||
|
urls[0]);
|
||||||
QCOMPARE(result[4]->url(), fullReference);
|
QCOMPARE(result[4]->url(), fullReference);
|
||||||
QCOMPARE(result[4]->resolveMultiplePlaceholders(result[4]->url()), urls[0]); // Should be resolved to the main entry
|
QCOMPARE(result[4]->resolveMultiplePlaceholders(result[4]->url()), urls[0]); // Should be resolved to the main entry
|
||||||
QCOMPARE(result[5]->url(), partialReference);
|
QCOMPARE(result[5]->url(), partialReference);
|
||||||
@ -386,7 +388,7 @@ void TestBrowser::testSearchEntriesWithAdditionalURLs()
|
|||||||
auto entries = createEntries(urls, root);
|
auto entries = createEntries(urls, root);
|
||||||
|
|
||||||
// Add an additional URL to the first entry
|
// Add an additional URL to the first entry
|
||||||
entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://keepassxc.org");
|
entries.first()->attributes()->set(EntryAttributes::AdditionalUrlAttribute, "https://keepassxc.org");
|
||||||
|
|
||||||
auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
|
auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
|
||||||
QCOMPARE(result.length(), 1);
|
QCOMPARE(result.length(), 1);
|
||||||
@ -663,7 +665,7 @@ void TestBrowser::testBestMatchingWithAdditionalURLs()
|
|||||||
browserSettings()->setBestMatchOnly(true);
|
browserSettings()->setBestMatchOnly(true);
|
||||||
|
|
||||||
// Add an additional URL to the first entry
|
// Add an additional URL to the first entry
|
||||||
entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://test.github.com/anotherpage");
|
entries.first()->attributes()->set(EntryAttributes::AdditionalUrlAttribute, "https://test.github.com/anotherpage");
|
||||||
|
|
||||||
// The first entry should be triggered
|
// The first entry should be triggered
|
||||||
auto result = m_browserService->searchEntries(
|
auto result = m_browserService->searchEntries(
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
#include "browser/BrowserCbor.h"
|
#include "browser/BrowserCbor.h"
|
||||||
#include "browser/BrowserMessageBuilder.h"
|
#include "browser/BrowserMessageBuilder.h"
|
||||||
#include "browser/BrowserService.h"
|
#include "browser/BrowserService.h"
|
||||||
|
#include "core/Database.h"
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "core/Group.h"
|
||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@ -469,3 +472,27 @@ void TestPasskeys::testSetFlags()
|
|||||||
auto discouragedResult = browserPasskeys()->setFlagsFromJson(discouragedJson);
|
auto discouragedResult = browserPasskeys()->setFlagsFromJson(discouragedJson);
|
||||||
QCOMPARE(discouragedResult, 0x01);
|
QCOMPARE(discouragedResult, 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestPasskeys::testEntry()
|
||||||
|
{
|
||||||
|
Database db;
|
||||||
|
auto* root = db.rootGroup();
|
||||||
|
root->setUuid(QUuid::createUuid());
|
||||||
|
|
||||||
|
auto* group1 = new Group();
|
||||||
|
group1->setUuid(QUuid::createUuid());
|
||||||
|
group1->setParent(root);
|
||||||
|
|
||||||
|
auto* entry = new Entry();
|
||||||
|
entry->setGroup(root);
|
||||||
|
|
||||||
|
browserService()->addPasskeyToEntry(entry,
|
||||||
|
QString("example.com"),
|
||||||
|
QString("example.com"),
|
||||||
|
QString("username"),
|
||||||
|
QString("userId"),
|
||||||
|
QString("userHandle"),
|
||||||
|
QString("privateKey"));
|
||||||
|
|
||||||
|
QVERIFY(entry->hasPasskey());
|
||||||
|
}
|
||||||
|
@ -43,5 +43,7 @@ private slots:
|
|||||||
void testExtensions();
|
void testExtensions();
|
||||||
void testParseFlags();
|
void testParseFlags();
|
||||||
void testSetFlags();
|
void testSetFlags();
|
||||||
|
|
||||||
|
void testEntry();
|
||||||
};
|
};
|
||||||
#endif // KEEPASSXC_TESTPASSKEYS_H
|
#endif // KEEPASSXC_TESTPASSKEYS_H
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2023 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
|
||||||
@ -239,3 +239,30 @@ void TestTools::testConvertToRegex_data()
|
|||||||
<< input << static_cast<int>(Tools::RegexConvertOpts::WILDCARD_UNLIMITED_MATCH)
|
<< input << static_cast<int>(Tools::RegexConvertOpts::WILDCARD_UNLIMITED_MATCH)
|
||||||
<< QString(R"(te\|st.*t\?\[5\]\^\(test\)\;\'\,\.)");
|
<< QString(R"(te\|st.*t\?\[5\]\^\(test\)\;\'\,\.)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestTools::testArrayContainsValues()
|
||||||
|
{
|
||||||
|
const auto values = QStringList() << "first"
|
||||||
|
<< "second"
|
||||||
|
<< "third";
|
||||||
|
|
||||||
|
// One missing
|
||||||
|
const auto result1 = Tools::getMissingValuesFromList<QString>(values,
|
||||||
|
QStringList() << "first"
|
||||||
|
<< "second"
|
||||||
|
<< "none");
|
||||||
|
QCOMPARE(result1.length(), 1);
|
||||||
|
QCOMPARE(result1.first(), QString("none"));
|
||||||
|
|
||||||
|
// All found
|
||||||
|
const auto result2 = Tools::getMissingValuesFromList<QString>(values,
|
||||||
|
QStringList() << "first"
|
||||||
|
<< "second"
|
||||||
|
<< "third");
|
||||||
|
QCOMPARE(result2.length(), 0);
|
||||||
|
|
||||||
|
// None are found
|
||||||
|
const auto numberValues = QList<int>({1, 2, 3, 4, 5});
|
||||||
|
const auto result3 = Tools::getMissingValuesFromList<int>(numberValues, QList<int>({6, 7, 8}));
|
||||||
|
QCOMPARE(result3.length(), 3);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2023 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
|
||||||
@ -35,6 +35,7 @@ private slots:
|
|||||||
void testEscapeRegex_data();
|
void testEscapeRegex_data();
|
||||||
void testConvertToRegex();
|
void testConvertToRegex();
|
||||||
void testConvertToRegex_data();
|
void testConvertToRegex_data();
|
||||||
|
void testArrayContainsValues();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_TESTTOOLS_H
|
#endif // KEEPASSX_TESTTOOLS_H
|
||||||
|
Loading…
Reference in New Issue
Block a user